Library Statements

library(ISLR)
library(dplyr)
library(readr)
library(broom)
library(ggplot2)
library(tidymodels)
library(stringr)
library(splitstackshape)
library(lubridate)
library(rpart.plot)
library(cluster)
library(forcats)
tidymodels_prefer()
library(probably)  #install.packages('probably')
library(vip)

Dataset

imdb_top_1000 <- read_csv("~/Desktop/Statistical Machine Learning/R Files/Final Project/imdb_top_1000_CLEAN.csv")

Data Cleaning

imdb_clean <- imdb_top_1000 %>%
    cSplit("Genre", sep = ",", direction = "wide") %>%
    mutate(Gross = log(Revenue - Budget))

runtime_clean <- imdb_top_1000$Runtime %>%
    str_replace(" min", "") %>%
    as.numeric()

imdb_clean$Runtime <- runtime_clean

imdb_clean <- imdb_clean %>%
    filter(Gross != "-Inf") %>%
    drop_na(Gross, Budget)

Data

head(imdb_clean)

Regression Models

Ordinary Linear Regression Model

Creation of CV Folds

data_cv10 <- vfold_cv(imdb_clean, v = 10)

Model Spec, Recipes, and Workflows

# Model Spec

lm_spec <- linear_reg() %>%
    set_engine(engine = "lm") %>%
    set_mode("regression")

# Recipe

full_lm_rec <- recipe(Gross ~ Runtime + IMDB_Rating + Meta_score + No_of_Votes +
    Genre_1, data = imdb_clean) %>%
    step_nzv(all_predictors()) %>%
    step_normalize(all_numeric_predictors()) %>%
    step_dummy(all_nominal_predictors()) %>%
    step_naomit(Gross, Runtime, IMDB_Rating, Meta_score, No_of_Votes)

# Workflow

full_lm_wf <- workflow() %>%
    add_recipe(full_lm_rec) %>%
    add_model(lm_spec)

Fit Full Model

# All predictors included

full_lm_model <- fit(full_lm_wf, data = imdb_clean)

full_lm_model %>%
    tidy()

Obtain Evaluation Metrics for Full Model

# Cross Validation Performed (10 folds)

full_lm_modelcv <- fit_resamples(full_lm_wf, resamples = data_cv10, metrics = metric_set(rmse,
    rsq, mae))

full_lm_modelcv %>%
    collect_metrics()

Perform LASSO for Subset Selection (Regression Model)

Model Spec, Recipes, and Workflow

# Lasso Model Spec with tune

lm_lasso_spec_tune <- 
  linear_reg() %>%
  set_args(mixture = 1, penalty = tune()) %>%   # mixture = 1 indicates Lasso
  set_engine(engine = 'glmnet') %>%             
  set_mode('regression') 

# Recipe

data_rec_lasso <- recipe(Gross ~ Runtime + IMDB_Rating + Meta_score + 
                   No_of_Votes + Genre_1, data = imdb_clean) %>%
    step_nzv(all_predictors()) %>%                # removes variables with the same value (don't want duplicates)
    step_novel(all_nominal_predictors()) %>%      # important if you have rare categorical variables 
    step_normalize(all_numeric_predictors()) %>%  # standardization important step for LASSO
    step_dummy(all_nominal_predictors()) %>%      # creates indicator variables for categorical variables
    step_naomit(Gross, Runtime, IMDB_Rating,      # omit any NA values
                Meta_score, No_of_Votes)                            

# Workflow

lasso_wf_tune <- workflow() %>% 
  add_recipe(data_rec_lasso) %>%
  add_model(lm_lasso_spec_tune) 

Tune Model and Cross Validation

# Tune Model (to select best Lambda penalty) -- via Cross Validation

penalty_grid <- grid_regular(penalty(range = c(-3, 1)), levels = 30)

tune_res <- tune_grid(lasso_wf_tune, resamples = data_cv10, metrics = metric_set(rmse,
    mae), grid = penalty_grid)

# Visualize Model Evaluation Metrics from Tuning

autoplot(tune_res) + theme_classic()

# Collect CV Metrics and Select Best Model

# Summarize Model Evaluation Metrics (CV)
lasso_mod <- collect_metrics(tune_res) %>%
    filter(.metric == "rmse") %>%
    select(penalty, rmse = mean)

# Choose penalty value
best_penalty <- select_best(tune_res, metric = "rmse")

lasso_mod

Fit Final LASSO Model

# Fit Final Model -- Subset of Variables

final_wf <- finalize_workflow(lasso_wf_tune, best_penalty)  # incorporates penalty value to workflow

final_fit <- fit(final_wf, data = imdb_clean)

tidy(final_fit)
# Final ('best') model predictors and coefficients

final_fit %>%
    tidy() %>%
    filter(estimate != 0)

Obtain Evaluation Metrics for Lasso Model

# Cross Validation Performed (10 folds)

lasso_fit <- fit_resamples(final_wf, resamples = data_cv10, metrics = metric_set(rmse,
    rsq, mae))

lasso_fit %>%
    collect_metrics()

Visualize Residuals for LASSO Model

lasso_mod_out <- final_fit %>%
    predict(new_data = imdb_clean) %>%
    bind_cols(imdb_clean) %>%
    mutate(resid = Gross - .pred)

ggplot(lasso_mod_out, aes(x = .pred, y = resid)) + geom_point() + geom_smooth() +
    geom_hline(yintercept = 0, color = "red") + theme_classic()

ggplot(lasso_mod_out, aes(x = Runtime, y = resid)) + geom_point() + geom_smooth() +
    geom_hline(yintercept = 0, color = "red") + theme_classic()

ggplot(lasso_mod_out, aes(x = IMDB_Rating, y = resid)) + geom_point() + geom_smooth() +
    geom_hline(yintercept = 0, color = "red") + theme_classic()

ggplot(lasso_mod_out, aes(x = No_of_Votes, y = resid)) + geom_point() + geom_smooth() +
    geom_hline(yintercept = 0, color = "red") + theme_classic()

GAM with Splines (TidyModels)

Build the GAM

# Build the GAM

gam_spec <- gen_additive_mod() %>%
    set_engine(engine = "mgcv") %>%
    set_mode("regression")

gam_mod1 <- fit(gam_spec, Gross ~ s(Runtime, k = 20) + s(IMDB_Rating) + Meta_score +
    s(No_of_Votes) + Genre_1, data = imdb_clean)

Run Diagnostics

# Diagnostics: Check to see if the number of knots is large enough (if p-value
# is low, increase number of knots)

gam_mod1 %>%
    pluck("fit") %>%
    mgcv::gam.check()

## 
## Method: GCV   Optimizer: magic
## Smoothing parameter selection converged after 9 iterations.
## The RMS GCV score gradient at convergence was 3.345103e-06 .
## The Hessian was positive definite.
## Model rank =  51 / 51 
## 
## Basis dimension (k) checking results. Low p-value (k-index<1) may
## indicate that k is too low, especially if edf is close to k'.
## 
##                   k'   edf k-index p-value  
## s(Runtime)     19.00 14.98    0.97    0.24  
## s(IMDB_Rating)  9.00  4.25    0.94    0.08 .
## s(No_of_Votes)  9.00  3.99    1.06    0.90  
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
# Diagnostics: Check to see if the number of knots is large enough

gam_mod1 %>%
    pluck("fit") %>%
    summary()
## 
## Family: gaussian 
## Link function: identity 
## 
## Formula:
## Gross ~ s(Runtime, k = 20) + s(IMDB_Rating) + Meta_score + s(No_of_Votes) + 
##     Genre_1
## 
## Parametric coefficients:
##                   Estimate Std. Error t value Pr(>|t|)    
## (Intercept)      16.832784   0.429340  39.206  < 2e-16 ***
## Meta_score        0.010241   0.005299   1.932  0.05387 .  
## Genre_1Adventure -0.044096   0.247036  -0.179  0.85840    
## Genre_1Animation  1.346910   0.301529   4.467 9.81e-06 ***
## Genre_1Biography  0.223135   0.234887   0.950  0.34258    
## Genre_1Comedy     0.267256   0.227633   1.174  0.24093    
## Genre_1Crime     -0.581492   0.232807  -2.498  0.01282 *  
## Genre_1Drama     -0.334477   0.190120  -1.759  0.07914 .  
## Genre_1Family    -0.175638   0.962267  -0.183  0.85524    
## Genre_1Film-Noir -2.558791   0.976808  -2.620  0.00907 ** 
## Genre_1Horror     0.512855   0.502375   1.021  0.30781    
## Genre_1Mystery   -1.175425   0.571692  -2.056  0.04029 *  
## Genre_1Thriller  -0.520050   1.354114  -0.384  0.70110    
## Genre_1Western   -0.455014   0.817789  -0.556  0.57819    
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Approximate significance of smooth terms:
##                   edf Ref.df      F p-value    
## s(Runtime)     14.979 17.114  5.202  <2e-16 ***
## s(IMDB_Rating)  4.254  5.211 19.128  <2e-16 ***
## s(No_of_Votes)  3.989  4.958 68.466  <2e-16 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## R-sq.(adj) =  0.521   Deviance explained = 55.4%
## GCV = 1.9035  Scale est. = 1.7723    n = 540

Visualizations for Non-Linear Functions

# Visualize: Look at the estimated non-linear functions

gam_mod1 %>%
    pluck("fit") %>%
    plot(main = "Non-Linear Visualizations")

Obtain Evaluation Metrics for GAM1

gam1_output <- gam_mod1 %>%
    predict(new_data = imdb_clean) %>%
    bind_cols(imdb_clean) %>%
    mutate(resid = Gross - .pred)

gam1_output %>%
    rmse(truth = Gross, estimate = .pred)
gam1_output %>%
    rsq(truth = Gross, estimate = .pred)
# Note: Unable to obtain std error for eval metrics bc can't perform cross
# validation w/ TidyModels

GAM with Splines (Recipe)

Build the GAM

spline_rec <- recipe(Gross ~ Runtime + IMDB_Rating + Meta_score + No_of_Votes + Genre_1,
    data = imdb_clean) %>%
    step_nzv(all_predictors()) %>%
    step_normalize(all_numeric_predictors()) %>%
    step_dummy(all_nominal_predictors()) %>%
    step_naomit(Gross) %>%
    step_ns(Runtime, deg_free = 20) %>%
    step_ns(No_of_Votes, deg_free = 10) %>%
    step_ns(IMDB_Rating, deg_free = 10)
# Chose degrees of freedom to attempt to match # of knots from TidyModels May
# not be an exact match, but it's close


spline_rec %>%
    prep(imdb_clean) %>%
    juice()
# Build the GAM

lm_spec_gam <- linear_reg() %>%
    set_engine(engine = "lm") %>%
    set_mode("regression")

spline_wf <- workflow() %>%
    add_model(lm_spec) %>%
    add_recipe(spline_rec)

GAM2_fit <- fit(spline_wf, data = imdb_clean)

tidy(GAM2_fit)

Obtain Evaluation Metrics for GAM2

# Cross Validation Performed (10 folds)

cv_output_spline2 <- fit_resamples( 
  spline_wf, # workflow
  resamples = data_cv10, # cv folds
  metrics = metric_set(mae,rmse,rsq)
)

cv_output_spline2 %>% collect_metrics()

Compare GAMs

# Compare RMSE values --> pick model with lower RMSE

gam1_output %>%
    rmse(truth = Gross, estimate = .pred)
cv_output_spline2 %>%
    collect_metrics()

The GAM created using TidyModels performs better than the recipe GAM based on RMSE. Likely has to do with the degrees of freedom of the splines. However, it is worth noting that we were unable to perform cross validation on the TidyModels GAM.

Visualize Residuals for Final GAM (GAM1)

# Visualize Residuals for Final GAM (TidyModels GAM)

ggplot(gam1_output, aes(x = .pred, y = resid)) + geom_point() + geom_smooth() + geom_hline(yintercept = 0,
    color = "red") + labs(x = "Prediction", y = "Residuals", title = "Residuals vs. Predictions") +
    theme_classic()

ggplot(gam1_output, aes(x = Runtime, y = resid)) + geom_point() + geom_smooth() +
    geom_hline(yintercept = 0, color = "red") + labs(x = "Runtime", y = "Residuals",
    title = "Residuals vs. Runtime") + theme_classic()

ggplot(gam1_output, aes(x = IMDB_Rating, y = resid)) + geom_point() + geom_smooth() +
    geom_hline(yintercept = 0, color = "red") + labs(x = "IMDB Rating", y = "Residuals",
    title = "Residuals vs. IMDB Rating") + theme_classic()

ggplot(gam1_output, aes(x = No_of_Votes, y = resid)) + geom_point() + geom_smooth() +
    geom_hline(yintercept = 0, color = "red") + labs(x = "Number of Votes", y = "Residuals",
    title = "Residuals vs. Number of Votes") + theme_classic()

There does not appear to be any significant bias after analyzing the residuals. The trendline for Residuals vs. Runtime trends up at the end points since there are only a few cases with very short and very long runtimes.

Compare All Regression Model Performance

# Eval Metrics from CV

full_lm_modelcv %>%
    collect_metrics()  # OLS
# Eval Metrics from CV

lasso_fit %>%
    collect_metrics()  # LASSO
# Eval Metrics from CV

gam1_output %>%
    rmse(truth = Gross, estimate = .pred)  # GAM

The GAM with Splines obtained using TidyModels performs the best with the lowest RMSE value (approx. 1.3 compared to approx. 1.4 for the others).

Classification Models

Create New Dataset for Classification

# Remove log transformation from Gross

imdb_class <- imdb_top_1000 %>%
    cSplit("Genre", sep = ",", direction = "wide") %>%
    mutate(Gross = Revenue - Budget)

runtime_clean <- imdb_top_1000$Runtime %>%
    str_replace(" min", "") %>%
    as.numeric()

imdb_class$Runtime <- runtime_clean

imdb_class <- imdb_class %>%
    drop_na(Gross, Budget)

imdb_clean_class <- imdb_class %>%
    mutate(success_ratio = Revenue/Budget) %>%
    mutate(flop = as.factor(ifelse(success_ratio > 2, "FALSE", "TRUE"))) %>%
    drop_na(flop, No_of_Votes, Runtime, IMDB_Rating, Meta_score, Genre_1)

Random Forests Model

Model Spec, Recipe, and Workflow

# Model Specification
rf_spec <- rand_forest() %>%
  set_engine(engine = 'ranger') %>% 
  set_args(trees = 1000, # Number of trees
           min_n = NULL,
           probability = FALSE,
           importance = 'impurity') %>%
  set_mode('classification')

# Recipe
data_rec <- recipe(flop ~ No_of_Votes + Runtime + IMDB_Rating + Meta_score + Genre_1,
                   data = imdb_clean_class) %>%
  step_naomit(flop, No_of_Votes, Runtime, IMDB_Rating, Meta_score, Genre_1)

# Create Workflows to Test Various Values for mtry

 data_wf_mtry3 <- workflow() %>%
  add_model(rf_spec %>% set_args(mtry = 3)) %>%
  add_recipe(data_rec) 

data_wf_mtry4 <- workflow() %>%
  add_model(rf_spec %>% set_args(mtry = 4)) %>%
  add_recipe(data_rec) 

data_wf_mtry5 <- workflow() %>%
  add_model(rf_spec %>% set_args(mtry = 5)) %>%
  add_recipe(data_rec)

Fit Models with Various Values for mtry

# Fit Models (for each mtry value)

set.seed(123)
data_fit_mtry3 <- fit(data_wf_mtry3, data = imdb_clean_class)

set.seed(123)
data_fit_mtry4 <- fit(data_wf_mtry4, data = imdb_clean_class)

set.seed(123)
data_fit_mtry5 <- fit(data_wf_mtry5, data = imdb_clean_class)

Obtain OOB Predictions and Evaluation Metrics to Choose mtry Value

# Custom Function to get OOB predictions, true observed outcomes and add a user-provided model label

rf_OOB_output <- function(fit_model, model_label, truth){
    tibble(
          .pred_class = fit_model %>% extract_fit_engine() %>% pluck('predictions'), #OOB predictions
          flop = truth,
          model = model_label
      )
}
# Evaluate OOB Metrics

data_rf_OOB_output <- bind_rows(rf_OOB_output(data_fit_mtry3, 3, imdb_clean_class %>%
    pull(flop)), rf_OOB_output(data_fit_mtry4, 4, imdb_clean_class %>%
    pull(flop)), rf_OOB_output(data_fit_mtry5, 5, imdb_clean_class %>%
    pull(flop)))

# OOB Accuracy for Each mtry Value

data_rf_OOB_output %>%
    group_by(model) %>%
    accuracy(truth = flop, estimate = .pred_class)
data_rf_OOB_output %>%
    group_by(model) %>%
    sensitivity(truth = flop, estimate = .pred_class)
# mtry = 3 and 4 tied for best sensitivity

# Accuracy vs. mtry Plot

data_rf_OOB_output %>%
    group_by(model) %>%
    accuracy(truth = flop, estimate = .pred_class) %>%
    mutate(mtry = as.numeric(stringr::str_replace(model, "mtry", ""))) %>%
    ggplot(aes(x = mtry, y = .estimate)) + geom_point() + geom_line() + theme_classic()

# Select mtry = 4 based on accuracy

Evaluating Selected Model - Confusion Matrix and Evaluation Metrics

# Confusion Matrix

rf_OOB_output(data_fit_mtry4, 4, imdb_clean_class %>%
    pull(flop)) %>%
    conf_mat(truth = flop, estimate = .pred_class)
##           Truth
## Prediction FALSE TRUE
##      FALSE   428  120
##      TRUE     47   33
# Sensitivity, Specificity, and Accuracy

rf_OOB_output(data_fit_mtry4, 4, imdb_clean_class %>%
    pull(flop)) %>%
    sensitivity(truth = flop, estimate = .pred_class)
rf_OOB_output(data_fit_mtry4, 4, imdb_clean_class %>%
    pull(flop)) %>%
    specificity(truth = flop, estimate = .pred_class)
rf_OOB_output(data_fit_mtry4, 4, imdb_clean_class %>%
    pull(flop)) %>%
    accuracy(truth = flop, estimate = .pred_class)

Variable Importance

Impurity

# Impurity

model_output <- data_fit_mtry4 %>%
    extract_fit_engine()

model_output %>%
    vip(num_features = 10) + theme_classic()  #based on impurity, 10 meaning the top 10

model_output %>%
    vip::vi() %>%
    head()
model_output %>%
    vip::vi() %>%
    tail()

Permuation

# Permutation

model_output2 <- data_wf_mtry4 %>% 
  update_model(rf_spec %>% set_args(importance = "permutation")) %>% #based on permutation
  fit(data = imdb_clean_class) %>% 
    extract_fit_engine() 

model_output2 %>% 
    vip(num_features = 10) + theme_classic()

model_output2 %>% vip::vi() %>% head()
model_output2 %>% vip::vi() %>% tail()

Violin Graphs

ggplot(imdb_clean_class, aes(x = flop, y = No_of_Votes)) + geom_violin() + theme_classic()

ggplot(imdb_clean_class, aes(x = flop, y = Runtime)) + geom_violin() + theme_classic()

ggplot(imdb_clean_class, aes(x = flop, y = IMDB_Rating)) + geom_violin() + theme_classic()

ggplot(imdb_clean_class, aes(x = flop, y = Meta_score)) + geom_violin() + theme_classic()

Logistic Regression

Model Spec, Recipe, and Workflow

set.seed(123)

# Logistic Regression Model Spec
logistic_spec <- logistic_reg() %>%
    set_engine("glm") %>%
    set_mode("classification")

# Recipe
logistic_rec <- recipe(flop ~ No_of_Votes + Runtime + IMDB_Rating + Genre_1, data = imdb_clean_class)

# Workflow (Recipe + Model) for Full Log Model
log_wf <- workflow() %>%
    add_recipe(logistic_rec) %>%
    add_model(logistic_spec)

Fit Model

# Fit Model

log_fit <- fit(log_wf, data = imdb_clean_class)

tidy(log_fit)

Add Variable for Odds Ratio

log_fit %>%
    tidy() %>%
    mutate(OR = exp(estimate))

Cross Validation and Evaluation Metrics

# Creation of CV Folds

data_cv10_class <- vfold_cv(imdb_clean_class, v = 10)
# Cross Validation Performed (10 folds)

log_modelcv <- fit_resamples(log_wf, resamples = data_cv10_class, metrics = metric_set(accuracy,
    sens, yardstick::spec))

log_modelcv %>%
    collect_metrics()

Picking Threshold

Boxplots

final_output <- log_fit %>%
    predict(new_data = imdb_clean_class, type = "prob") %>%
    bind_cols(imdb_clean_class)

final_output %>%
    ggplot(aes(x = flop, y = .pred_TRUE)) + geom_boxplot()

ROC Curve

# Use soft predictions
final_output %>%
    roc_curve(flop, .pred_TRUE, event_level = "second") %>%
    autoplot()

J Index vs. Threshold

# Thresholds in terms of reference level

threshold_output <- final_output %>%
    threshold_perf(truth = flop, estimate = .pred_FALSE, thresholds = seq(0, 1, by = 0.01))

# J-index v. Threshold for no flop

threshold_output %>%
    filter(.metric == "j_index") %>%
    ggplot(aes(x = .threshold, y = .estimate)) + geom_line() + labs(y = "J-index",
    x = "threshold") + theme_classic()

threshold_output %>%
    filter(.metric == "j_index") %>%
    arrange(desc(.estimate))

Distance vs. Threshold

# Distance vs. Threshold

threshold_output %>%
    filter(.metric == "distance") %>%
    ggplot(aes(x = .threshold, y = .estimate)) + geom_line() + labs(y = "Distance",
    x = "threshold") + theme_classic()

threshold_output %>%
    filter(.metric == "distance") %>%
    arrange(.estimate)

Obtain Evaluation Metrics for Logistic Regression Model with Thresholds

# To determine final threshold

log_metrics <- metric_set(accuracy, sens, yardstick::spec)

# Compare Eval Metrics

final_output %>%
    mutate(.pred_class = make_two_class_pred(.pred_FALSE, levels(flop), threshold = 0.78)) %>%
    log_metrics(truth = flop, estimate = .pred_class, event_level = "second")
final_output %>%
    mutate(.pred_class = make_two_class_pred(.pred_FALSE, levels(flop), threshold = 0.71)) %>%
    log_metrics(truth = flop, estimate = .pred_class, event_level = "second")
# Compare Confusion Matrices

final_output %>%
    mutate(.pred_class = make_two_class_pred(.pred_FALSE, levels(flop), threshold = 0.78)) %>%
    conf_mat(truth = flop, estimate = .pred_class)
##           Truth
## Prediction FALSE TRUE
##      FALSE   234   18
##      TRUE    241  135
final_output %>%
    mutate(.pred_class = make_two_class_pred(.pred_FALSE, levels(flop), threshold = 0.71)) %>%
    conf_mat(truth = flop, estimate = .pred_class)
##           Truth
## Prediction FALSE TRUE
##      FALSE   323   54
##      TRUE    152   99

We chose the threshold of 0.78 for our final model because we are prioritizing sensitivity over accuracy and specificity.

Predictions

predict(log_fit, new_data = data.frame(No_of_Votes = 10000, Runtime = 112, IMDB_Rating = 9.8,
    Genre_1 = "Drama"), type = "prob")

We manually performed the hard predictions with threshold = .78. Since .pred_FALSE = 0.56 which is lower than our threshold of 0.78, we would predict this example movie would flop (flop = TRUE).

Unsupervised Learning - Clustering

K-Means Clustering

Preliminary Visualizations

ggplot(imdb_clean, aes(x = Budget, y = Runtime)) + geom_point() + theme_classic()

imdb_clean %>%
    filter(Budget > 1) %>%
    ggplot(aes(x = Budget, y = Gross)) + geom_point() + labs(x = "Budget in USD",
    y = "Gross Profit in USD (Log Scale)", title = "Preliminary Visualizations") +
    theme_classic()

ggplot(imdb_clean, aes(x = No_of_Votes, y = Runtime)) + geom_point() + theme_classic()

ggplot(imdb_clean, aes(x = Gross, y = No_of_Votes)) + geom_point() + theme_classic()

Feature Selection

imdb_sub <- imdb_clean %>%
    select(Budget, Gross)

set.seed(253)

Determine Number of Clusters

# Data-specific function to cluster and calculate total within-cluster SS
imdb_cluster_ss <- function(k) {
    # Perform clustering
    kclust <- kmeans(scale(imdb_sub), centers = k)

    # Return the total within-cluster sum of squares
    return(kclust$tot.withinss)
}

tibble(k = 1:15, tot_wc_ss = purrr::map_dbl(1:15, imdb_cluster_ss)) %>%
    ggplot(aes(x = k, y = tot_wc_ss)) + geom_point() + labs(x = "Number of clusters",
    y = "Total within-cluster sum of squares") + theme_classic()

Select k = 8 Clusters

kclust_k8 <- kmeans(scale(imdb_sub), centers = 8)

kclust_k8$cluster  # Display cluster assignments
##   [1] 5 3 8 1 7 6 3 3 8 2 6 3 5 6 6 2 8 1 6 6 3 3 3 3 5 5 2 6 2 6 5 1 1 3 6 5 3
##  [38] 1 5 5 6 8 2 1 8 1 3 1 2 2 5 5 1 5 1 7 6 2 2 5 3 3 3 6 1 1 3 1 2 2 1 2 1 2
##  [75] 1 2 1 5 1 5 7 7 1 5 3 1 1 6 8 6 1 2 8 2 5 7 8 2 6 5 2 6 2 2 3 2 5 5 5 5 5
## [112] 1 7 7 7 7 1 1 2 3 6 3 3 2 7 1 2 8 3 6 1 7 8 1 3 3 1 1 5 3 6 3 1 6 3 6 1 1
## [149] 2 2 5 6 2 3 1 2 7 5 5 5 5 1 1 1 2 1 5 1 1 7 5 2 7 1 5 7 5 5 3 5 4 7 1 2 3
## [186] 2 6 8 7 3 8 1 1 3 2 6 1 3 3 1 3 6 6 2 5 8 1 2 1 6 8 6 6 5 5 1 5 1 5 3 7 2
## [223] 2 2 3 3 3 3 1 3 1 7 2 2 1 1 3 1 2 1 3 1 2 2 1 1 2 1 3 2 1 5 1 5 7 1 5 5 7
## [260] 7 1 7 1 7 7 7 5 1 3 5 8 2 8 1 5 5 3 1 5 1 8 1 2 5 6 5 8 1 5 2 6 1 6 6 1 5
## [297] 5 1 1 2 1 5 1 2 1 1 2 2 2 1 1 5 6 2 2 4 5 5 1 1 5 5 5 3 5 2 8 2 8 2 5 5 2
## [334] 8 2 6 2 3 8 2 2 3 5 1 8 1 2 2 6 8 2 2 2 8 1 2 3 2 1 2 5 2 2 6 6 8 2 5 1 1
## [371] 5 3 5 1 2 1 5 3 1 2 5 2 2 2 1 1 3 3 2 1 5 7 1 5 1 1 2 2 1 5 5 7 2 3 3 5 5
## [408] 6 3 5 8 5 5 8 7 2 6 6 8 5 3 3 8 3 3 2 8 2 5 1 1 2 1 8 5 3 6 5 2 2 8 6 2 8
## [445] 1 2 1 6 6 2 2 5 2 5 5 3 6 5 2 5 5 3 1 1 2 3 2 6 1 5 5 5 1 2 2 2 2 1 2 1 2
## [482] 1 1 1 1 1 1 1 5 2 5 3 5 5 2 1 5 8 3 3 2 3 1 2 8 5 1 1 6 1 2 1 2 2 5 6 3 5
## [519] 3 5 6 3 1 2 8 6 2 6 2 7 5 2 2 2 2 1 2 2 6 2 1 5 2 6 7 3 2 6 3 3 1 7 2 3 2
## [556] 2 3 2 2 1 1 1 2 1 5 1 3 7 5 5 1 1
imdb_clean <- imdb_clean %>%
    mutate(kclust_8 = factor(kclust_k8$cluster))

Visualize Cluster Assignments

# Visualize the cluster assignments on the original scatterplot
imdb_clean %>%
    ggplot(aes(x = Budget, y = Gross, color = kclust_8)) + geom_point() + theme_classic()

Interpreting Clusters

Exploring Genre Breakdown

# Count of Movies per Genre (Primary Genre)
imdb_clean %>%
    count(Genre_1)
# Count of Movies per Genre (Secondary Genre)
imdb_clean %>%
    count(Genre_2)
# Count of Movies per Genre (Overall Genre)
imdb_clean %>%
    count(New_Genre)
# Genres vs Cluster

# How many of each Genre 1 in each cluster
imdb_clean %>%
    group_by(kclust_8) %>%
    count(Genre_1)
# How many of each Genre 2 in each cluster
imdb_clean %>%
    group_by(kclust_8) %>%
    count(Genre_2)
# How many movies in each cluster
imdb_clean %>%
    count(kclust_8)

Visualizations of Genres in Each Cluster

# Genre 1
imdb_clean %>%
    ggplot(aes(x = kclust_8, fill = Genre_1)) + geom_bar(position = "fill") + labs(x = "Cluster") +
    theme_classic()

# Genre 2
imdb_clean %>%
    ggplot(aes(x = kclust_8, fill = Genre_2)) + geom_bar(position = "fill") + labs(x = "Cluster") +
    theme_classic()

# Overall Genre
imdb_clean %>%
    ggplot(aes(x = kclust_8, fill = New_Genre)) + geom_bar(position = "fill") + labs(x = "Cluster") +
    theme_classic()

Hierarchical Clustering

Set up Clustering and Distance Matrix

# Random subsample of 25 Movies
set.seed(253)

imdb_hc <- imdb_clean %>%
    slice_sample(n = 25) %>%
    filter(Budget != 0)

# Select the variables to be used in clustering
imdb_hc_sub <- imdb_hc %>%
    select(Gross, Budget)

imdb_hc_full <- imdb_clean %>%
    select(Gross, Budget) %>%
    filter(Budget > 1)

# Summary statistics for the variables
summary(imdb_hc_sub)
##      Gross           Budget         
##  Min.   :13.82   Min.   :   560000  
##  1st Qu.:15.91   1st Qu.:  3000000  
##  Median :17.15   Median : 19250000  
##  Mean   :17.32   Mean   : 40957083  
##  3rd Qu.:18.89   3rd Qu.: 60000000  
##  Max.   :20.31   Max.   :190000000
# Compute a distance matrix on the scaled data
dist_mat_scaled <- dist(scale(imdb_hc_sub))  # Subset Distance Matrix

dist_mat_full <- dist(scale(imdb_hc_full))  # Full Data Distance Matrix

Perform Fusing Process

imdb_hc_avg <- hclust(dist_mat_scaled, method = "average")  # Subset
imdb_full_avg <- hclust(dist_mat_full, method = "average")  # Full Data

Visualize Dendrograms

# Plot dendrogram on Subset
plot(imdb_hc_avg)

# Adding Genre Labels

plot(imdb_hc_avg, labels = imdb_hc$Genre_1, main = "Movie Clusters", xlab = NULL)

plot(imdb_hc_avg, labels = paste(imdb_hc$Genre_1, imdb_hc$Genre_2))

plot(imdb_hc_avg, main = "Visualizing Movie Clusters", labels = paste(imdb_hc$Genre_1,
    imdb_hc$Genre_2, imdb_hc$Genre_3), hang = -1, cex = 1)

plot(imdb_hc_avg, labels = paste(imdb_hc$New_Genre))

Cutting the Tree (Choosing k)

imdb_clean_clust <- imdb_clean %>%
  filter(Budget > 1) %>%
    mutate(
        hclust_num2 = factor(cutree(imdb_full_avg, k = 2)), # Cut into 2 clusters (k)
        hclust_num4 = factor(cutree(imdb_full_avg, k = 4)), # Cut into 4 clusters (k)
        hclust_num8 = factor(cutree(imdb_full_avg, k = 8)) # Cut into 8 clusters (k)
    )
ggplot(imdb_clean_clust, aes(x = hclust_num2, fill = Genre_1)) + geom_bar(position = "fill") +
    labs(x = "Cluster", y = "Proportion of Cluster", title = "Selecting Number of Clusters (k)") +
    theme_classic() + theme(plot.title = element_text(size = 20, face = "bold"))

ggplot(imdb_clean_clust, aes(x = hclust_num4, fill = Genre_1)) + geom_bar(position = "fill") +
    labs(x = "Cluster", y = "Proportion of Cluster", title = "Selecting Number of Clusters (k)") +
    theme_classic() + theme(plot.title = element_text(size = 20, face = "bold"))

ggplot(imdb_clean_clust, aes(x = hclust_num8, fill = Genre_1)) + geom_bar(position = "fill") +
    labs(x = "Cluster", y = "Proportion of Cluster", title = "Selecting Number of Clusters (k)") +
    theme_classic() + theme(plot.title = element_text(size = 20, face = "bold"))

Visualizing Genres in Final Clusters (Full Data)

ggplot(imdb_clean_clust, aes(x = hclust_num2, fill = Genre_1)) + geom_bar(position = "fill") +
    labs(x = "Cluster", y = "Proportion of Cluster", title = "Selecting Number of Clusters (k)",
        fill = "Genre 1") + theme_classic() + theme(plot.title = element_text(size = 20,
    face = "bold"))

ggplot(imdb_clean_clust, aes(x = hclust_num2, fill = New_Genre)) + geom_bar(position = "fill") +
    labs(x = "Cluster") + theme_classic()

ggplot(imdb_clean_clust, aes(x = Budget, y = Gross, color = hclust_num2)) + geom_point() +
    labs(x = "Budget in USD", y = "Gross Profit in USD (Log Scale)", title = "Visualizing Clusters: Gross Profit vs. Budget",
        color = "Clusters") + theme_classic() + theme(plot.title = element_text(size = 20,
    face = "bold"))

ggplot(imdb_clean_clust, aes(x = hclust_num2, y = Budget)) + geom_boxplot() + labs(x = "Cluster",
    y = "Budget in USD", title = "Visualizing Clusters: Budget") + theme_classic() +
    theme(plot.title = element_text(size = 20, face = "bold"))

ggplot(imdb_clean_clust, aes(x = hclust_num2, y = Gross)) + geom_boxplot() + labs(x = "Cluster",
    y = "Gross Profit in USD (Log Scale)", title = "Visualizing Clusters: Gross Profit") +
    theme_classic() + theme(plot.title = element_text(size = 20, face = "bold"))

imdb_clean_clust %>%
    count(hclust_num2)
imdb_clean_clust %>%
    group_by(hclust_num2) %>%
    summarize(mean(Gross), sd(Gross), min(Gross), max(Gross), mean((Budget/1e+05)),
        sd((Budget/1e+05)), min((Budget/1e+05)), max((Budget/1e+05)))
LS0tCnRpdGxlOiAiRmluYWwgUHJvamVjdCBDb2RlIgphdXRob3I6ICJFbWlseSwgSnVsaWFuLCBKYWNvYiwgYW5kIEFyaXN0byIKZGF0ZTogJzIwMjItMTEtMjAnCm91dHB1dDogCiAgaHRtbF9kb2N1bWVudDoKICAgIGRmX3ByaW50OiBwYWdlZAogICAgdG9jOiB0cnVlCiAgICBjb2RlX2Rvd25sb2FkOiB0cnVlCiAgICB0aGVtZTogcGFwZXIKLS0tCgpgYGB7ciBzZXR1cCwgaW5jbHVkZT1GQUxTRX0Ka25pdHI6Om9wdHNfY2h1bmskc2V0KGVjaG8gPSBUUlVFLCBldmFsID0gVFJVRSwgd2FybmluZyA9IEZBTFNFLCBtZXNzYWdlID0gRkFMU0UsIHRpZHkgPSBUUlVFKQpgYGAKCiMgTGlicmFyeSBTdGF0ZW1lbnRzIAoKYGBge3J9CmxpYnJhcnkoSVNMUikKbGlicmFyeShkcGx5cikKbGlicmFyeShyZWFkcikKbGlicmFyeShicm9vbSkKbGlicmFyeShnZ3Bsb3QyKQpsaWJyYXJ5KHRpZHltb2RlbHMpIApsaWJyYXJ5KHN0cmluZ3IpCmxpYnJhcnkoc3BsaXRzdGFja3NoYXBlKQpsaWJyYXJ5KGx1YnJpZGF0ZSkKbGlicmFyeShycGFydC5wbG90KQpsaWJyYXJ5KGNsdXN0ZXIpCmxpYnJhcnkoZm9yY2F0cykKdGlkeW1vZGVsc19wcmVmZXIoKQpsaWJyYXJ5KHByb2JhYmx5KSAjaW5zdGFsbC5wYWNrYWdlcygncHJvYmFibHknKQpsaWJyYXJ5KHZpcCkKYGBgCgojIERhdGFzZXQKCmBgYHtyfQppbWRiX3RvcF8xMDAwIDwtIHJlYWRfY3N2KCJ+L0Rlc2t0b3AvU3RhdGlzdGljYWwgTWFjaGluZSBMZWFybmluZy9SIEZpbGVzL0ZpbmFsIFByb2plY3QvaW1kYl90b3BfMTAwMF9DTEVBTi5jc3YiKQpgYGAKCiMjIERhdGEgQ2xlYW5pbmcKCmBgYHtyfQppbWRiX2NsZWFuIDwtIGltZGJfdG9wXzEwMDAgJT4lCiAgY1NwbGl0KCJHZW5yZSIsIHNlcCA9ICIsIiwgZGlyZWN0aW9uID0gIndpZGUiKSAlPiUKICBtdXRhdGUoR3Jvc3MgPSBsb2coUmV2ZW51ZS1CdWRnZXQpKQoKcnVudGltZV9jbGVhbiA8LSBpbWRiX3RvcF8xMDAwJFJ1bnRpbWUgJT4lCiAgc3RyX3JlcGxhY2UoIiBtaW4iLCAiIikgJT4lCiAgYXMubnVtZXJpYygpCgppbWRiX2NsZWFuJFJ1bnRpbWUgPC0gcnVudGltZV9jbGVhbgoKaW1kYl9jbGVhbiA8LSBpbWRiX2NsZWFuICU+JQogIGZpbHRlcihHcm9zcyAhPSAiLUluZiIpICU+JQogIGRyb3BfbmEoR3Jvc3MsIEJ1ZGdldCkKYGBgCgojIyBEYXRhCgpgYGB7cn0KaGVhZChpbWRiX2NsZWFuKQpgYGAKCiMgUmVncmVzc2lvbiBNb2RlbHMKCiMjIE9yZGluYXJ5IExpbmVhciBSZWdyZXNzaW9uIE1vZGVsCgojIyMgQ3JlYXRpb24gb2YgQ1YgRm9sZHMKCmBgYHtyfQpkYXRhX2N2MTAgPC0gdmZvbGRfY3YoaW1kYl9jbGVhbiwgdiA9IDEwKQpgYGAKCiMjIyBNb2RlbCBTcGVjLCBSZWNpcGVzLCBhbmQgV29ya2Zsb3dzCgpgYGB7cn0KIyBNb2RlbCBTcGVjCgpsbV9zcGVjIDwtCiAgICBsaW5lYXJfcmVnKCkgJT4lIAogICAgc2V0X2VuZ2luZShlbmdpbmUgPSAnbG0nKSAlPiUgCiAgICBzZXRfbW9kZSgncmVncmVzc2lvbicpCgojIFJlY2lwZQoKZnVsbF9sbV9yZWMgPC0gcmVjaXBlKEdyb3NzIH4gUnVudGltZSArIElNREJfUmF0aW5nICsgTWV0YV9zY29yZSArIAogICAgICAgICAgICAgICAgICAgTm9fb2ZfVm90ZXMgKyBHZW5yZV8xLCBkYXRhID0gaW1kYl9jbGVhbikgJT4lCiAgICBzdGVwX256dihhbGxfcHJlZGljdG9ycygpKSAlPiUgCiAgICBzdGVwX25vcm1hbGl6ZShhbGxfbnVtZXJpY19wcmVkaWN0b3JzKCkpICU+JSAKICAgIHN0ZXBfZHVtbXkoYWxsX25vbWluYWxfcHJlZGljdG9ycygpKSAlPiUKICAgIHN0ZXBfbmFvbWl0KEdyb3NzLCBSdW50aW1lLCBJTURCX1JhdGluZywgTWV0YV9zY29yZSwgTm9fb2ZfVm90ZXMpCgojIFdvcmtmbG93CgpmdWxsX2xtX3dmIDwtIHdvcmtmbG93KCkgJT4lCiAgICBhZGRfcmVjaXBlKGZ1bGxfbG1fcmVjKSAlPiUKICAgIGFkZF9tb2RlbChsbV9zcGVjKQpgYGAKCiMjIyBGaXQgRnVsbCBNb2RlbAoKYGBge3J9CiMgQWxsIHByZWRpY3RvcnMgaW5jbHVkZWQKCmZ1bGxfbG1fbW9kZWwgPC0gZml0KGZ1bGxfbG1fd2YsIGRhdGEgPSBpbWRiX2NsZWFuKSAKCmZ1bGxfbG1fbW9kZWwgJT4lIHRpZHkoKQpgYGAKCiMjIyBPYnRhaW4gRXZhbHVhdGlvbiBNZXRyaWNzIGZvciBGdWxsIE1vZGVsCgpgYGB7cn0KIyBDcm9zcyBWYWxpZGF0aW9uIFBlcmZvcm1lZCAoMTAgZm9sZHMpCgpmdWxsX2xtX21vZGVsY3YgPC0gZml0X3Jlc2FtcGxlcyhmdWxsX2xtX3dmLCByZXNhbXBsZXMgPSBkYXRhX2N2MTAsIG1ldHJpY3MgPSBtZXRyaWNfc2V0KHJtc2UsIHJzcSwgbWFlKSkKCmZ1bGxfbG1fbW9kZWxjdiAlPiUKICBjb2xsZWN0X21ldHJpY3MoKQpgYGAKCiMjIFBlcmZvcm0gTEFTU08gZm9yIFN1YnNldCBTZWxlY3Rpb24gKFJlZ3Jlc3Npb24gTW9kZWwpCgojIyMgTW9kZWwgU3BlYywgUmVjaXBlcywgYW5kIFdvcmtmbG93CgpgYGB7cn0KIyBMYXNzbyBNb2RlbCBTcGVjIHdpdGggdHVuZQoKbG1fbGFzc29fc3BlY190dW5lIDwtIAogIGxpbmVhcl9yZWcoKSAlPiUKICBzZXRfYXJncyhtaXh0dXJlID0gMSwgcGVuYWx0eSA9IHR1bmUoKSkgJT4lICAgIyBtaXh0dXJlID0gMSBpbmRpY2F0ZXMgTGFzc28KICBzZXRfZW5naW5lKGVuZ2luZSA9ICdnbG1uZXQnKSAlPiUgICAgICAgICAgICAgCiAgc2V0X21vZGUoJ3JlZ3Jlc3Npb24nKSAKCiMgUmVjaXBlCgpkYXRhX3JlY19sYXNzbyA8LSByZWNpcGUoR3Jvc3MgfiBSdW50aW1lICsgSU1EQl9SYXRpbmcgKyBNZXRhX3Njb3JlICsgCiAgICAgICAgICAgICAgICAgICBOb19vZl9Wb3RlcyArIEdlbnJlXzEsIGRhdGEgPSBpbWRiX2NsZWFuKSAlPiUKICAgIHN0ZXBfbnp2KGFsbF9wcmVkaWN0b3JzKCkpICU+JSAgICAgICAgICAgICAgICAjIHJlbW92ZXMgdmFyaWFibGVzIHdpdGggdGhlIHNhbWUgdmFsdWUgKGRvbid0IHdhbnQgZHVwbGljYXRlcykKICAgIHN0ZXBfbm92ZWwoYWxsX25vbWluYWxfcHJlZGljdG9ycygpKSAlPiUgICAgICAjIGltcG9ydGFudCBpZiB5b3UgaGF2ZSByYXJlIGNhdGVnb3JpY2FsIHZhcmlhYmxlcyAKICAgIHN0ZXBfbm9ybWFsaXplKGFsbF9udW1lcmljX3ByZWRpY3RvcnMoKSkgJT4lICAjIHN0YW5kYXJkaXphdGlvbiBpbXBvcnRhbnQgc3RlcCBmb3IgTEFTU08KICAgIHN0ZXBfZHVtbXkoYWxsX25vbWluYWxfcHJlZGljdG9ycygpKSAlPiUgICAgICAjIGNyZWF0ZXMgaW5kaWNhdG9yIHZhcmlhYmxlcyBmb3IgY2F0ZWdvcmljYWwgdmFyaWFibGVzCiAgICBzdGVwX25hb21pdChHcm9zcywgUnVudGltZSwgSU1EQl9SYXRpbmcsICAgICAgIyBvbWl0IGFueSBOQSB2YWx1ZXMKICAgICAgICAgICAgICAgIE1ldGFfc2NvcmUsIE5vX29mX1ZvdGVzKSAgICAgICAgICAgICAgICAgICAgICAgICAgICAKCiMgV29ya2Zsb3cKCmxhc3NvX3dmX3R1bmUgPC0gd29ya2Zsb3coKSAlPiUgCiAgYWRkX3JlY2lwZShkYXRhX3JlY19sYXNzbykgJT4lCiAgYWRkX21vZGVsKGxtX2xhc3NvX3NwZWNfdHVuZSkgCmBgYAoKIyMjIFR1bmUgTW9kZWwgYW5kIENyb3NzIFZhbGlkYXRpb24KCmBgYHtyfQojIFR1bmUgTW9kZWwgKHRvIHNlbGVjdCBiZXN0IExhbWJkYSBwZW5hbHR5KSAtLSB2aWEgQ3Jvc3MgVmFsaWRhdGlvbgoKcGVuYWx0eV9ncmlkIDwtIGdyaWRfcmVndWxhcigKICBwZW5hbHR5KHJhbmdlID0gYygtMywgMSkpLAogIGxldmVscyA9IDMwKQoKdHVuZV9yZXMgPC0gdHVuZV9ncmlkKAogIGxhc3NvX3dmX3R1bmUsIAogIHJlc2FtcGxlcyA9IGRhdGFfY3YxMCwgCiAgbWV0cmljcyA9IG1ldHJpY19zZXQocm1zZSwgbWFlKSwKICBncmlkID0gcGVuYWx0eV9ncmlkIAopCgojIFZpc3VhbGl6ZSBNb2RlbCBFdmFsdWF0aW9uIE1ldHJpY3MgZnJvbSBUdW5pbmcKCmF1dG9wbG90KHR1bmVfcmVzKSArIHRoZW1lX2NsYXNzaWMoKQoKIyBDb2xsZWN0IENWIE1ldHJpY3MgYW5kIFNlbGVjdCBCZXN0IE1vZGVsCgojIFN1bW1hcml6ZSBNb2RlbCBFdmFsdWF0aW9uIE1ldHJpY3MgKENWKQpsYXNzb19tb2QgPC0gY29sbGVjdF9tZXRyaWNzKHR1bmVfcmVzKSAlPiUKICBmaWx0ZXIoLm1ldHJpYyA9PSAncm1zZScpICU+JQogIHNlbGVjdChwZW5hbHR5LCBybXNlID0gbWVhbikgCgojIENob29zZSBwZW5hbHR5IHZhbHVlCmJlc3RfcGVuYWx0eSA8LSBzZWxlY3RfYmVzdCh0dW5lX3JlcywgbWV0cmljID0gJ3Jtc2UnKQoKbGFzc29fbW9kCmBgYAoKIyMjIEZpdCBGaW5hbCBMQVNTTyBNb2RlbAoKYGBge3J9CiMgRml0IEZpbmFsIE1vZGVsIC0tIFN1YnNldCBvZiBWYXJpYWJsZXMKCmZpbmFsX3dmIDwtIGZpbmFsaXplX3dvcmtmbG93KGxhc3NvX3dmX3R1bmUsIGJlc3RfcGVuYWx0eSkgIyBpbmNvcnBvcmF0ZXMgcGVuYWx0eSB2YWx1ZSB0byB3b3JrZmxvdwoKZmluYWxfZml0IDwtIGZpdChmaW5hbF93ZiwgZGF0YSA9IGltZGJfY2xlYW4pCgp0aWR5KGZpbmFsX2ZpdCkKCiMgRmluYWwgKCJiZXN0IikgbW9kZWwgcHJlZGljdG9ycyBhbmQgY29lZmZpY2llbnRzCgpmaW5hbF9maXQgJT4lIHRpZHkoKSAlPiUgZmlsdGVyKGVzdGltYXRlICE9IDApCmBgYAoKIyMjIE9idGFpbiBFdmFsdWF0aW9uIE1ldHJpY3MgZm9yIExhc3NvIE1vZGVsCgpgYGB7cn0KIyBDcm9zcyBWYWxpZGF0aW9uIFBlcmZvcm1lZCAoMTAgZm9sZHMpCgpsYXNzb19maXQgPC0gZml0X3Jlc2FtcGxlcyhmaW5hbF93ZiwgcmVzYW1wbGVzID0gZGF0YV9jdjEwLCBtZXRyaWNzID0gbWV0cmljX3NldChybXNlLCByc3EsIG1hZSkpCgpsYXNzb19maXQgJT4lCiAgY29sbGVjdF9tZXRyaWNzKCkKYGBgCgojIyMgVmlzdWFsaXplIFJlc2lkdWFscyBmb3IgTEFTU08gTW9kZWwKCmBgYHtyfQpsYXNzb19tb2Rfb3V0IDwtIGZpbmFsX2ZpdCAlPiUKICAgIHByZWRpY3QobmV3X2RhdGEgPSBpbWRiX2NsZWFuKSAlPiUKICAgIGJpbmRfY29scyhpbWRiX2NsZWFuKSAlPiUKICAgIG11dGF0ZShyZXNpZCA9IEdyb3NzIC0gLnByZWQpCgpnZ3Bsb3QobGFzc29fbW9kX291dCwgYWVzKHggPSAucHJlZCwgeSA9IHJlc2lkKSkgKwogICAgZ2VvbV9wb2ludCgpICsKICAgIGdlb21fc21vb3RoKCkgKwogICAgZ2VvbV9obGluZSh5aW50ZXJjZXB0ID0gMCwgY29sb3IgPSAicmVkIikgKyAKICAgIHRoZW1lX2NsYXNzaWMoKQoKZ2dwbG90KGxhc3NvX21vZF9vdXQsIGFlcyh4ID0gUnVudGltZSwgeSA9IHJlc2lkKSkgKwogICAgZ2VvbV9wb2ludCgpICsKICAgIGdlb21fc21vb3RoKCkgKwogICAgZ2VvbV9obGluZSh5aW50ZXJjZXB0ID0gMCwgY29sb3IgPSAicmVkIikgKyAKICAgIHRoZW1lX2NsYXNzaWMoKQoKZ2dwbG90KGxhc3NvX21vZF9vdXQsIGFlcyh4ID0gSU1EQl9SYXRpbmcsIHkgPSByZXNpZCkpICsKICAgIGdlb21fcG9pbnQoKSArCiAgICBnZW9tX3Ntb290aCgpICsKICAgIGdlb21faGxpbmUoeWludGVyY2VwdCA9IDAsIGNvbG9yID0gInJlZCIpICsgCiAgICB0aGVtZV9jbGFzc2ljKCkKCmdncGxvdChsYXNzb19tb2Rfb3V0LCBhZXMoeCA9IE5vX29mX1ZvdGVzLCB5ID0gcmVzaWQpKSArCiAgICBnZW9tX3BvaW50KCkgKwogICAgZ2VvbV9zbW9vdGgoKSArCiAgICBnZW9tX2hsaW5lKHlpbnRlcmNlcHQgPSAwLCBjb2xvciA9ICJyZWQiKSArIAogICAgdGhlbWVfY2xhc3NpYygpCmBgYAoKIyMgR0FNIHdpdGggU3BsaW5lcyAoVGlkeU1vZGVscykKCiMjIyBCdWlsZCB0aGUgR0FNCgpgYGB7cn0KIyBCdWlsZCB0aGUgR0FNCgpnYW1fc3BlYyA8LSAKICBnZW5fYWRkaXRpdmVfbW9kKCkgJT4lCiAgc2V0X2VuZ2luZShlbmdpbmUgPSAnbWdjdicpICU+JQogIHNldF9tb2RlKCdyZWdyZXNzaW9uJykgCgpnYW1fbW9kMSA8LSBmaXQoZ2FtX3NwZWMsCiAgICBHcm9zcyB+IHMoUnVudGltZSwgayA9IDIwKSArIHMoSU1EQl9SYXRpbmcpICsgTWV0YV9zY29yZSArIHMoTm9fb2ZfVm90ZXMpICsgR2VucmVfMSwKICAgIGRhdGEgPSBpbWRiX2NsZWFuIAopCgpgYGAKCiMjIyBSdW4gRGlhZ25vc3RpY3MKCmBgYGB7cn0KIyBEaWFnbm9zdGljczogQ2hlY2sgdG8gc2VlIGlmIHRoZSBudW1iZXIgb2Yga25vdHMgaXMgbGFyZ2UgZW5vdWdoIChpZiBwLXZhbHVlIGlzIGxvdywgaW5jcmVhc2UgbnVtYmVyIG9mIGtub3RzKQoKZ2FtX21vZDEgJT4lIHBsdWNrKCdmaXQnKSAlPiUgbWdjdjo6Z2FtLmNoZWNrKCkKYGBgCgpgYGB7cn0KIyBEaWFnbm9zdGljczogQ2hlY2sgdG8gc2VlIGlmIHRoZSBudW1iZXIgb2Yga25vdHMgaXMgbGFyZ2UgZW5vdWdoCgpnYW1fbW9kMSAlPiUgcGx1Y2soJ2ZpdCcpICU+JSBzdW1tYXJ5KCkgCmBgYAoKIyMjIFZpc3VhbGl6YXRpb25zIGZvciBOb24tTGluZWFyIEZ1bmN0aW9ucwoKYGBge3J9CiMgVmlzdWFsaXplOiBMb29rIGF0IHRoZSBlc3RpbWF0ZWQgbm9uLWxpbmVhciBmdW5jdGlvbnMKCmdhbV9tb2QxICU+JSBwbHVjaygnZml0JykgJT4lIHBsb3QobWFpbiA9ICJOb24tTGluZWFyIFZpc3VhbGl6YXRpb25zIikKYGBgCgojIyMgT2J0YWluIEV2YWx1YXRpb24gTWV0cmljcyBmb3IgR0FNMQoKYGBge3J9CmdhbTFfb3V0cHV0IDwtIGdhbV9tb2QxJT4lIAogICAgcHJlZGljdChuZXdfZGF0YSA9IGltZGJfY2xlYW4pICU+JQogICAgYmluZF9jb2xzKGltZGJfY2xlYW4pICU+JQogICAgbXV0YXRlKHJlc2lkID0gR3Jvc3MgLSAucHJlZCkKCmdhbTFfb3V0cHV0ICU+JQogICAgcm1zZSh0cnV0aCA9IEdyb3NzLCBlc3RpbWF0ZSA9IC5wcmVkKQoKZ2FtMV9vdXRwdXQgJT4lCiAgICByc3EodHJ1dGggPSBHcm9zcywgZXN0aW1hdGUgPSAucHJlZCkKCiMgTm90ZTogVW5hYmxlIHRvIG9idGFpbiBzdGQgZXJyb3IgZm9yIGV2YWwgbWV0cmljcyBiYyBjYW4ndCBwZXJmb3JtIGNyb3NzIHZhbGlkYXRpb24gdy8gVGlkeU1vZGVscwpgYGAKCiMjIEdBTSB3aXRoIFNwbGluZXMgKFJlY2lwZSkKCiMjIyBCdWlsZCB0aGUgR0FNCgpgYGB7cn0Kc3BsaW5lX3JlYyA8LSByZWNpcGUoR3Jvc3MgfiBSdW50aW1lICsgSU1EQl9SYXRpbmcgKyBNZXRhX3Njb3JlICsgCiAgICAgICAgICAgICAgICAgICBOb19vZl9Wb3RlcyArIEdlbnJlXzEsIGRhdGEgPSBpbWRiX2NsZWFuKSAlPiUKICAgIHN0ZXBfbnp2KGFsbF9wcmVkaWN0b3JzKCkpICU+JSAKICAgIHN0ZXBfbm9ybWFsaXplKGFsbF9udW1lcmljX3ByZWRpY3RvcnMoKSkgJT4lIAogICAgc3RlcF9kdW1teShhbGxfbm9taW5hbF9wcmVkaWN0b3JzKCkpICU+JQogICAgc3RlcF9uYW9taXQoR3Jvc3MpICU+JQogICAgc3RlcF9ucyhSdW50aW1lLCBkZWdfZnJlZSA9IDIwKSAlPiUKICAgIHN0ZXBfbnMoTm9fb2ZfVm90ZXMsIGRlZ19mcmVlID0gMTApICU+JQogICAgc3RlcF9ucyhJTURCX1JhdGluZywgZGVnX2ZyZWUgPSAxMCkKIyBDaG9zZSBkZWdyZWVzIG9mIGZyZWVkb20gdG8gYXR0ZW1wdCB0byBtYXRjaCAjIG9mIGtub3RzIGZyb20gVGlkeU1vZGVscwojIE1heSBub3QgYmUgYW4gZXhhY3QgbWF0Y2gsIGJ1dCBpdCdzIGNsb3NlCgoKc3BsaW5lX3JlYyAlPiUgcHJlcChpbWRiX2NsZWFuKSAlPiUganVpY2UoKQpgYGAKCmBgYHtyfQojIEJ1aWxkIHRoZSBHQU0KCmxtX3NwZWNfZ2FtIDwtCiAgbGluZWFyX3JlZygpICU+JQogIHNldF9lbmdpbmUoZW5naW5lID0gJ2xtJykgJT4lCiAgc2V0X21vZGUoJ3JlZ3Jlc3Npb24nKQoKc3BsaW5lX3dmIDwtIHdvcmtmbG93KCkgJT4lCiAgICBhZGRfbW9kZWwobG1fc3BlYykgJT4lCiAgICBhZGRfcmVjaXBlKHNwbGluZV9yZWMpCgpHQU0yX2ZpdCA8LSBmaXQoc3BsaW5lX3dmLCBkYXRhID0gaW1kYl9jbGVhbikKCnRpZHkoR0FNMl9maXQpCmBgYAoKIyMjIE9idGFpbiBFdmFsdWF0aW9uIE1ldHJpY3MgZm9yIEdBTTIKCmBgYHtyfQojIENyb3NzIFZhbGlkYXRpb24gUGVyZm9ybWVkICgxMCBmb2xkcykKCmN2X291dHB1dF9zcGxpbmUyIDwtIGZpdF9yZXNhbXBsZXMoIAogIHNwbGluZV93ZiwgIyB3b3JrZmxvdwogIHJlc2FtcGxlcyA9IGRhdGFfY3YxMCwgIyBjdiBmb2xkcwogIG1ldHJpY3MgPSBtZXRyaWNfc2V0KG1hZSxybXNlLHJzcSkKKQoKY3Zfb3V0cHV0X3NwbGluZTIgJT4lIGNvbGxlY3RfbWV0cmljcygpCmBgYAoKIyMgQ29tcGFyZSBHQU1zCgpgYGB7cn0KIyBDb21wYXJlIFJNU0UgdmFsdWVzIC0tPiBwaWNrIG1vZGVsIHdpdGggbG93ZXIgUk1TRQoKZ2FtMV9vdXRwdXQgJT4lCiAgICBybXNlKHRydXRoID0gR3Jvc3MsIGVzdGltYXRlID0gLnByZWQpCgpjdl9vdXRwdXRfc3BsaW5lMiAlPiUgY29sbGVjdF9tZXRyaWNzKCkKYGBgCgpUaGUgR0FNIGNyZWF0ZWQgdXNpbmcgVGlkeU1vZGVscyBwZXJmb3JtcyBiZXR0ZXIgdGhhbiB0aGUgcmVjaXBlIEdBTSBiYXNlZCBvbiBSTVNFLiBMaWtlbHkgaGFzIHRvIGRvIHdpdGggdGhlIGRlZ3JlZXMgb2YgZnJlZWRvbSBvZiB0aGUgc3BsaW5lcy4gSG93ZXZlciwgaXQgaXMgd29ydGggbm90aW5nIHRoYXQgd2Ugd2VyZSB1bmFibGUgdG8gcGVyZm9ybSBjcm9zcyB2YWxpZGF0aW9uIG9uIHRoZSBUaWR5TW9kZWxzIEdBTS4KCiMjIyBWaXN1YWxpemUgUmVzaWR1YWxzIGZvciBGaW5hbCBHQU0gKEdBTTEpCgpgYGB7cn0KIyBWaXN1YWxpemUgUmVzaWR1YWxzIGZvciBGaW5hbCBHQU0gKFRpZHlNb2RlbHMgR0FNKQoKZ2dwbG90KGdhbTFfb3V0cHV0LCBhZXMoeCA9IC5wcmVkLCB5ID0gcmVzaWQpKSArCiAgICBnZW9tX3BvaW50KCkgKwogICAgZ2VvbV9zbW9vdGgoKSArCiAgICBnZW9tX2hsaW5lKHlpbnRlcmNlcHQgPSAwLCBjb2xvciA9ICJyZWQiKSArIAogICAgbGFicyh4ID0gIlByZWRpY3Rpb24iLAogICAgICAgICB5ID0gIlJlc2lkdWFscyIsIAogICAgICAgICB0aXRsZSA9ICJSZXNpZHVhbHMgdnMuIFByZWRpY3Rpb25zIikgKwogICAgdGhlbWVfY2xhc3NpYygpCgpnZ3Bsb3QoZ2FtMV9vdXRwdXQsIGFlcyh4ID0gUnVudGltZSwgeSA9IHJlc2lkKSkgKwogICAgZ2VvbV9wb2ludCgpICsKICAgIGdlb21fc21vb3RoKCkgKwogICAgZ2VvbV9obGluZSh5aW50ZXJjZXB0ID0gMCwgY29sb3IgPSAicmVkIikgICsgCiAgICBsYWJzKHggPSAiUnVudGltZSIsCiAgICAgICAgIHkgPSAiUmVzaWR1YWxzIiwgCiAgICAgICAgIHRpdGxlID0gIlJlc2lkdWFscyB2cy4gUnVudGltZSIpICsKICAgIHRoZW1lX2NsYXNzaWMoKQoKZ2dwbG90KGdhbTFfb3V0cHV0LCBhZXMoeCA9IElNREJfUmF0aW5nLCB5ID0gcmVzaWQpKSArCiAgICBnZW9tX3BvaW50KCkgKwogICAgZ2VvbV9zbW9vdGgoKSArCiAgICBnZW9tX2hsaW5lKHlpbnRlcmNlcHQgPSAwLCBjb2xvciA9ICJyZWQiKSAgKyAKICAgIGxhYnMoeCA9ICJJTURCIFJhdGluZyIsCiAgICAgICAgIHkgPSAiUmVzaWR1YWxzIiwgCiAgICAgICAgIHRpdGxlID0gIlJlc2lkdWFscyB2cy4gSU1EQiBSYXRpbmciKSArCiAgICB0aGVtZV9jbGFzc2ljKCkKCmdncGxvdChnYW0xX291dHB1dCwgYWVzKHggPSBOb19vZl9Wb3RlcywgeSA9IHJlc2lkKSkgKwogICAgZ2VvbV9wb2ludCgpICsKICAgIGdlb21fc21vb3RoKCkgKwogICAgZ2VvbV9obGluZSh5aW50ZXJjZXB0ID0gMCwgY29sb3IgPSAicmVkIikgICsgCiAgICBsYWJzKHggPSAiTnVtYmVyIG9mIFZvdGVzIiwKICAgICAgICAgeSA9ICJSZXNpZHVhbHMiLCAKICAgICAgICAgdGl0bGUgPSAiUmVzaWR1YWxzIHZzLiBOdW1iZXIgb2YgVm90ZXMiKSArCiAgICB0aGVtZV9jbGFzc2ljKCkKYGBgCgpUaGVyZSBkb2VzIG5vdCBhcHBlYXIgdG8gYmUgYW55IHNpZ25pZmljYW50IGJpYXMgYWZ0ZXIgYW5hbHl6aW5nIHRoZSByZXNpZHVhbHMuIFRoZSB0cmVuZGxpbmUgZm9yIFJlc2lkdWFscyB2cy4gUnVudGltZSB0cmVuZHMgdXAgYXQgdGhlIGVuZCBwb2ludHMgc2luY2UgdGhlcmUgYXJlIG9ubHkgYSBmZXcgY2FzZXMgd2l0aCB2ZXJ5IHNob3J0IGFuZCB2ZXJ5IGxvbmcgcnVudGltZXMuCgojIyBDb21wYXJlIEFsbCBSZWdyZXNzaW9uIE1vZGVsIFBlcmZvcm1hbmNlCgpgYGB7cn0KIyBFdmFsIE1ldHJpY3MgZnJvbSBDVgoKZnVsbF9sbV9tb2RlbGN2ICU+JSBjb2xsZWN0X21ldHJpY3MoKSAjIE9MUwpgYGAKCmBgYHtyfQojIEV2YWwgTWV0cmljcyBmcm9tIENWCgpsYXNzb19maXQgJT4lIGNvbGxlY3RfbWV0cmljcygpICMgTEFTU08KYGBgCgpgYGB7cn0KIyBFdmFsIE1ldHJpY3MgZnJvbSBDVgoKZ2FtMV9vdXRwdXQgJT4lCiAgICBybXNlKHRydXRoID0gR3Jvc3MsIGVzdGltYXRlID0gLnByZWQpICMgR0FNCmBgYAoKVGhlIEdBTSB3aXRoIFNwbGluZXMgb2J0YWluZWQgdXNpbmcgVGlkeU1vZGVscyBwZXJmb3JtcyB0aGUgYmVzdCB3aXRoIHRoZSBsb3dlc3QgUk1TRSB2YWx1ZSAoYXBwcm94LiAxLjMgY29tcGFyZWQgdG8gYXBwcm94LiAxLjQgZm9yIHRoZSBvdGhlcnMpLgoKCiMgQ2xhc3NpZmljYXRpb24gTW9kZWxzCgojIyBDcmVhdGUgTmV3IERhdGFzZXQgZm9yIENsYXNzaWZpY2F0aW9uCgpgYGB7cn0KIyBSZW1vdmUgbG9nIHRyYW5zZm9ybWF0aW9uIGZyb20gR3Jvc3MKCmltZGJfY2xhc3MgPC0gaW1kYl90b3BfMTAwMCAlPiUKICBjU3BsaXQoIkdlbnJlIiwgc2VwID0gIiwiLCBkaXJlY3Rpb24gPSAid2lkZSIpICU+JQogIG11dGF0ZShHcm9zcyA9IFJldmVudWUtQnVkZ2V0KQoKcnVudGltZV9jbGVhbiA8LSBpbWRiX3RvcF8xMDAwJFJ1bnRpbWUgJT4lCiAgc3RyX3JlcGxhY2UoIiBtaW4iLCAiIikgJT4lCiAgYXMubnVtZXJpYygpCgppbWRiX2NsYXNzJFJ1bnRpbWUgPC0gcnVudGltZV9jbGVhbgoKaW1kYl9jbGFzcyA8LSBpbWRiX2NsYXNzICU+JQogIGRyb3BfbmEoR3Jvc3MsIEJ1ZGdldCkKCmltZGJfY2xlYW5fY2xhc3MgPC0gaW1kYl9jbGFzcyAlPiUKICBtdXRhdGUoc3VjY2Vzc19yYXRpbyA9IFJldmVudWUvQnVkZ2V0KSAlPiUKICBtdXRhdGUoZmxvcCA9IGFzLmZhY3RvcihpZmVsc2Uoc3VjY2Vzc19yYXRpbyA+IDIsICdGQUxTRScsICdUUlVFJykpKSAlPiUKICBkcm9wX25hKGZsb3AsIE5vX29mX1ZvdGVzLCBSdW50aW1lLCBJTURCX1JhdGluZywgTWV0YV9zY29yZSwgR2VucmVfMSkKYGBgCgojIyBSYW5kb20gRm9yZXN0cyBNb2RlbAoKIyMjIE1vZGVsIFNwZWMsIFJlY2lwZSwgYW5kIFdvcmtmbG93CgpgYGB7cn0KIyBNb2RlbCBTcGVjaWZpY2F0aW9uCnJmX3NwZWMgPC0gcmFuZF9mb3Jlc3QoKSAlPiUKICBzZXRfZW5naW5lKGVuZ2luZSA9ICdyYW5nZXInKSAlPiUgCiAgc2V0X2FyZ3ModHJlZXMgPSAxMDAwLCAjIE51bWJlciBvZiB0cmVlcwogICAgICAgICAgIG1pbl9uID0gTlVMTCwKICAgICAgICAgICBwcm9iYWJpbGl0eSA9IEZBTFNFLAogICAgICAgICAgIGltcG9ydGFuY2UgPSAnaW1wdXJpdHknKSAlPiUKICBzZXRfbW9kZSgnY2xhc3NpZmljYXRpb24nKQoKIyBSZWNpcGUKZGF0YV9yZWMgPC0gcmVjaXBlKGZsb3AgfiBOb19vZl9Wb3RlcyArIFJ1bnRpbWUgKyBJTURCX1JhdGluZyArIE1ldGFfc2NvcmUgKyBHZW5yZV8xLAogICAgICAgICAgICAgICAgICAgZGF0YSA9IGltZGJfY2xlYW5fY2xhc3MpICU+JQogIHN0ZXBfbmFvbWl0KGZsb3AsIE5vX29mX1ZvdGVzLCBSdW50aW1lLCBJTURCX1JhdGluZywgTWV0YV9zY29yZSwgR2VucmVfMSkKCiMgQ3JlYXRlIFdvcmtmbG93cyB0byBUZXN0IFZhcmlvdXMgVmFsdWVzIGZvciBtdHJ5CgogZGF0YV93Zl9tdHJ5MyA8LSB3b3JrZmxvdygpICU+JQogIGFkZF9tb2RlbChyZl9zcGVjICU+JSBzZXRfYXJncyhtdHJ5ID0gMykpICU+JQogIGFkZF9yZWNpcGUoZGF0YV9yZWMpIAoKZGF0YV93Zl9tdHJ5NCA8LSB3b3JrZmxvdygpICU+JQogIGFkZF9tb2RlbChyZl9zcGVjICU+JSBzZXRfYXJncyhtdHJ5ID0gNCkpICU+JQogIGFkZF9yZWNpcGUoZGF0YV9yZWMpIAoKZGF0YV93Zl9tdHJ5NSA8LSB3b3JrZmxvdygpICU+JQogIGFkZF9tb2RlbChyZl9zcGVjICU+JSBzZXRfYXJncyhtdHJ5ID0gNSkpICU+JQogIGFkZF9yZWNpcGUoZGF0YV9yZWMpCmBgYAoKIyMjIEZpdCBNb2RlbHMgd2l0aCBWYXJpb3VzIFZhbHVlcyBmb3IgbXRyeQoKYGBge3J9CiMgRml0IE1vZGVscyAoZm9yIGVhY2ggbXRyeSB2YWx1ZSkKCnNldC5zZWVkKDEyMykKZGF0YV9maXRfbXRyeTMgPC0gZml0KGRhdGFfd2ZfbXRyeTMsIGRhdGEgPSBpbWRiX2NsZWFuX2NsYXNzKQoKc2V0LnNlZWQoMTIzKSAKZGF0YV9maXRfbXRyeTQgPC0gZml0KGRhdGFfd2ZfbXRyeTQsIGRhdGEgPSBpbWRiX2NsZWFuX2NsYXNzKQoKc2V0LnNlZWQoMTIzKQpkYXRhX2ZpdF9tdHJ5NSA8LSBmaXQoZGF0YV93Zl9tdHJ5NSwgZGF0YSA9IGltZGJfY2xlYW5fY2xhc3MpCmBgYAoKIyMjIE9idGFpbiBPT0IgUHJlZGljdGlvbnMgYW5kIEV2YWx1YXRpb24gTWV0cmljcyB0byBDaG9vc2UgbXRyeSBWYWx1ZQoKYGBge3J9CiMgQ3VzdG9tIEZ1bmN0aW9uIHRvIGdldCBPT0IgcHJlZGljdGlvbnMsIHRydWUgb2JzZXJ2ZWQgb3V0Y29tZXMgYW5kIGFkZCBhIHVzZXItcHJvdmlkZWQgbW9kZWwgbGFiZWwKCnJmX09PQl9vdXRwdXQgPC0gZnVuY3Rpb24oZml0X21vZGVsLCBtb2RlbF9sYWJlbCwgdHJ1dGgpewogICAgdGliYmxlKAogICAgICAgICAgLnByZWRfY2xhc3MgPSBmaXRfbW9kZWwgJT4lIGV4dHJhY3RfZml0X2VuZ2luZSgpICU+JSBwbHVjaygncHJlZGljdGlvbnMnKSwgI09PQiBwcmVkaWN0aW9ucwogICAgICAgICAgZmxvcCA9IHRydXRoLAogICAgICAgICAgbW9kZWwgPSBtb2RlbF9sYWJlbAogICAgICApCn0KYGBgCgpgYGB7cn0KIyBFdmFsdWF0ZSBPT0IgTWV0cmljcwoKZGF0YV9yZl9PT0Jfb3V0cHV0IDwtIGJpbmRfcm93cygKICAgIHJmX09PQl9vdXRwdXQoZGF0YV9maXRfbXRyeTMsMywgaW1kYl9jbGVhbl9jbGFzcyAlPiUgcHVsbChmbG9wKSksCiAgICByZl9PT0Jfb3V0cHV0KGRhdGFfZml0X210cnk0LDQsIGltZGJfY2xlYW5fY2xhc3MgJT4lIHB1bGwoZmxvcCkpLAogICAgcmZfT09CX291dHB1dChkYXRhX2ZpdF9tdHJ5NSw1LCBpbWRiX2NsZWFuX2NsYXNzICU+JSBwdWxsKGZsb3ApKQopCgojIE9PQiBBY2N1cmFjeSBmb3IgRWFjaCBtdHJ5IFZhbHVlCgpkYXRhX3JmX09PQl9vdXRwdXQgJT4lIAogICAgZ3JvdXBfYnkobW9kZWwpICU+JQogICAgYWNjdXJhY3kodHJ1dGggPSBmbG9wLCBlc3RpbWF0ZSA9IC5wcmVkX2NsYXNzKQoKZGF0YV9yZl9PT0Jfb3V0cHV0ICU+JSAKICAgIGdyb3VwX2J5KG1vZGVsKSAlPiUKICAgIHNlbnNpdGl2aXR5KHRydXRoID0gZmxvcCwgZXN0aW1hdGUgPSAucHJlZF9jbGFzcykKIyBtdHJ5ID0gMyBhbmQgNCB0aWVkIGZvciBiZXN0IHNlbnNpdGl2aXR5CgojIEFjY3VyYWN5IHZzLiBtdHJ5IFBsb3QKCmRhdGFfcmZfT09CX291dHB1dCAlPiUgCiAgZ3JvdXBfYnkobW9kZWwpICU+JQogIGFjY3VyYWN5KHRydXRoID0gZmxvcCwgZXN0aW1hdGUgPSAucHJlZF9jbGFzcykgJT4lCiAgbXV0YXRlKG10cnkgPSBhcy5udW1lcmljKHN0cmluZ3I6OnN0cl9yZXBsYWNlKG1vZGVsLCdtdHJ5JywnJykpKSAlPiUKICBnZ3Bsb3QoYWVzKHggPSBtdHJ5LCB5ID0gLmVzdGltYXRlICkpICsgCiAgZ2VvbV9wb2ludCgpICsKICBnZW9tX2xpbmUoKSArCiAgdGhlbWVfY2xhc3NpYygpCiMgU2VsZWN0IG10cnkgPSA0IGJhc2VkIG9uIGFjY3VyYWN5CmBgYAoKIyMjIEV2YWx1YXRpbmcgU2VsZWN0ZWQgTW9kZWwgLSBDb25mdXNpb24gTWF0cml4IGFuZCBFdmFsdWF0aW9uIE1ldHJpY3MKCmBgYHtyfQojIENvbmZ1c2lvbiBNYXRyaXgKCnJmX09PQl9vdXRwdXQoZGF0YV9maXRfbXRyeTQsNCwgaW1kYl9jbGVhbl9jbGFzcyAlPiUgcHVsbChmbG9wKSkgJT4lCiAgICBjb25mX21hdCh0cnV0aCA9IGZsb3AsIGVzdGltYXRlPSAucHJlZF9jbGFzcykKCiMgU2Vuc2l0aXZpdHksIFNwZWNpZmljaXR5LCBhbmQgQWNjdXJhY3kKCnJmX09PQl9vdXRwdXQoZGF0YV9maXRfbXRyeTQsNCwgaW1kYl9jbGVhbl9jbGFzcyAlPiUgcHVsbChmbG9wKSkgJT4lCiAgc2Vuc2l0aXZpdHkodHJ1dGggPSBmbG9wLCBlc3RpbWF0ZT0gLnByZWRfY2xhc3MpCgpyZl9PT0Jfb3V0cHV0KGRhdGFfZml0X210cnk0LDQsIGltZGJfY2xlYW5fY2xhc3MgJT4lIHB1bGwoZmxvcCkpICU+JQogIHNwZWNpZmljaXR5KHRydXRoID0gZmxvcCwgZXN0aW1hdGU9IC5wcmVkX2NsYXNzKQoKcmZfT09CX291dHB1dChkYXRhX2ZpdF9tdHJ5NCw0LCBpbWRiX2NsZWFuX2NsYXNzICU+JSBwdWxsKGZsb3ApKSAlPiUKICBhY2N1cmFjeSh0cnV0aCA9IGZsb3AsIGVzdGltYXRlPSAucHJlZF9jbGFzcykKYGBgCgojIyMgVmFyaWFibGUgSW1wb3J0YW5jZQoKIyMjIyBJbXB1cml0eQoKYGBge3J9CiMgSW1wdXJpdHkKCm1vZGVsX291dHB1dCA8LWRhdGFfZml0X210cnk0ICU+JSAKICAgIGV4dHJhY3RfZml0X2VuZ2luZSgpIAoKbW9kZWxfb3V0cHV0ICU+JSAKICAgIHZpcChudW1fZmVhdHVyZXMgPSAxMCkgKyB0aGVtZV9jbGFzc2ljKCkgI2Jhc2VkIG9uIGltcHVyaXR5LCAxMCBtZWFuaW5nIHRoZSB0b3AgMTAKCm1vZGVsX291dHB1dCAlPiUgdmlwOjp2aSgpICU+JSBoZWFkKCkKbW9kZWxfb3V0cHV0ICU+JSB2aXA6OnZpKCkgJT4lIHRhaWwoKQpgYGAKCiMjIyMgUGVybXVhdGlvbgoKYGBge3J9CiMgUGVybXV0YXRpb24KCm1vZGVsX291dHB1dDIgPC0gZGF0YV93Zl9tdHJ5NCAlPiUgCiAgdXBkYXRlX21vZGVsKHJmX3NwZWMgJT4lIHNldF9hcmdzKGltcG9ydGFuY2UgPSAicGVybXV0YXRpb24iKSkgJT4lICNiYXNlZCBvbiBwZXJtdXRhdGlvbgogIGZpdChkYXRhID0gaW1kYl9jbGVhbl9jbGFzcykgJT4lIAogICAgZXh0cmFjdF9maXRfZW5naW5lKCkgCgptb2RlbF9vdXRwdXQyICU+JSAKICAgIHZpcChudW1fZmVhdHVyZXMgPSAxMCkgKyB0aGVtZV9jbGFzc2ljKCkKCgptb2RlbF9vdXRwdXQyICU+JSB2aXA6OnZpKCkgJT4lIGhlYWQoKQptb2RlbF9vdXRwdXQyICU+JSB2aXA6OnZpKCkgJT4lIHRhaWwoKQpgYGAKCiMjIyBWaW9saW4gR3JhcGhzCgpgYGB7cn0KZ2dwbG90KGltZGJfY2xlYW5fY2xhc3MsIGFlcyh4ID0gZmxvcCwgeSA9IE5vX29mX1ZvdGVzKSkgKwogICAgZ2VvbV92aW9saW4oKSArIHRoZW1lX2NsYXNzaWMoKQoKZ2dwbG90KGltZGJfY2xlYW5fY2xhc3MsIGFlcyh4ID0gZmxvcCwgeSA9IFJ1bnRpbWUpKSArCiAgICBnZW9tX3Zpb2xpbigpICsgdGhlbWVfY2xhc3NpYygpCgpnZ3Bsb3QoaW1kYl9jbGVhbl9jbGFzcywgYWVzKHggPSBmbG9wLCB5ID0gSU1EQl9SYXRpbmcpKSArCiAgICBnZW9tX3Zpb2xpbigpICsgdGhlbWVfY2xhc3NpYygpCgpnZ3Bsb3QoaW1kYl9jbGVhbl9jbGFzcywgYWVzKHggPSBmbG9wLCB5ID0gTWV0YV9zY29yZSkpICsKICAgIGdlb21fdmlvbGluKCkgKyB0aGVtZV9jbGFzc2ljKCkKYGBgCgojIyBMb2dpc3RpYyBSZWdyZXNzaW9uCgojIyMgTW9kZWwgU3BlYywgUmVjaXBlLCBhbmQgV29ya2Zsb3cKCmBgYHtyfQpzZXQuc2VlZCgxMjMpCgojIExvZ2lzdGljIFJlZ3Jlc3Npb24gTW9kZWwgU3BlYwpsb2dpc3RpY19zcGVjIDwtIGxvZ2lzdGljX3JlZygpICU+JQogICAgc2V0X2VuZ2luZSgnZ2xtJykgJT4lCiAgICBzZXRfbW9kZSgnY2xhc3NpZmljYXRpb24nKQoKIyBSZWNpcGUKbG9naXN0aWNfcmVjIDwtIHJlY2lwZShmbG9wIH4gTm9fb2ZfVm90ZXMgKyBSdW50aW1lICsgSU1EQl9SYXRpbmcgKyBHZW5yZV8xLAogICAgICAgICAgICAgICAgICAgZGF0YSA9IGltZGJfY2xlYW5fY2xhc3MpCgojIFdvcmtmbG93IChSZWNpcGUgKyBNb2RlbCkgZm9yIEZ1bGwgTG9nIE1vZGVsCmxvZ193ZiA8LSB3b3JrZmxvdygpICU+JQogICAgYWRkX3JlY2lwZShsb2dpc3RpY19yZWMpICU+JQogICAgYWRkX21vZGVsKGxvZ2lzdGljX3NwZWMpCmBgYAoKIyMjIEZpdCBNb2RlbAoKYGBge3J9CiMgRml0IE1vZGVsCgpsb2dfZml0IDwtIGZpdChsb2dfd2YsIGRhdGEgPSBpbWRiX2NsZWFuX2NsYXNzKQoKdGlkeShsb2dfZml0KQpgYGAKCiMjIyBBZGQgVmFyaWFibGUgZm9yIE9kZHMgUmF0aW8KCmBgYHtyfQpsb2dfZml0ICU+JSB0aWR5KCkgJT4lCiAgbXV0YXRlKE9SID0gZXhwKGVzdGltYXRlKSkKYGBgCgojIyMgQ3Jvc3MgVmFsaWRhdGlvbiBhbmQgRXZhbHVhdGlvbiBNZXRyaWNzCgpgYGB7cn0KIyBDcmVhdGlvbiBvZiBDViBGb2xkcwoKZGF0YV9jdjEwX2NsYXNzIDwtIHZmb2xkX2N2KGltZGJfY2xlYW5fY2xhc3MsIHYgPSAxMCkKYGBgCgpgYGB7cn0KIyBDcm9zcyBWYWxpZGF0aW9uIFBlcmZvcm1lZCAoMTAgZm9sZHMpCgpsb2dfbW9kZWxjdiA8LSBmaXRfcmVzYW1wbGVzKGxvZ193ZiwgcmVzYW1wbGVzID0gZGF0YV9jdjEwX2NsYXNzLCBtZXRyaWNzID0gbWV0cmljX3NldChhY2N1cmFjeSxzZW5zLHlhcmRzdGljazo6c3BlYykpCgpsb2dfbW9kZWxjdiAlPiUKICBjb2xsZWN0X21ldHJpY3MoKQpgYGAKCiMjIyBQaWNraW5nIFRocmVzaG9sZAoKIyMjIyBCb3hwbG90cwoKYGBge3J9CmZpbmFsX291dHB1dCA8LSBsb2dfZml0ICU+JSBwcmVkaWN0KG5ld19kYXRhID0gaW1kYl9jbGVhbl9jbGFzcywgdHlwZT0ncHJvYicpICU+JSBiaW5kX2NvbHMoaW1kYl9jbGVhbl9jbGFzcykKCmZpbmFsX291dHB1dCAlPiUKICBnZ3Bsb3QoYWVzKHggPSBmbG9wLCB5ID0gLnByZWRfVFJVRSkpICsKICBnZW9tX2JveHBsb3QoKQpgYGAKCiMjIyMgUk9DIEN1cnZlCgpgYGB7cn0KIyBVc2Ugc29mdCBwcmVkaWN0aW9ucwpmaW5hbF9vdXRwdXQgJT4lCiAgICByb2NfY3VydmUoZmxvcCwucHJlZF9UUlVFLGV2ZW50X2xldmVsID0gJ3NlY29uZCcpICU+JQogICAgYXV0b3Bsb3QoKQpgYGAKCiMjIyMgSiBJbmRleCB2cy4gVGhyZXNob2xkCgpgYGB7cn0KIyBUaHJlc2hvbGRzIGluIHRlcm1zIG9mIHJlZmVyZW5jZSBsZXZlbAoKdGhyZXNob2xkX291dHB1dCA8LSBmaW5hbF9vdXRwdXQgJT4lCiAgICB0aHJlc2hvbGRfcGVyZih0cnV0aCA9IGZsb3AsIGVzdGltYXRlID0gLnByZWRfRkFMU0UsIHRocmVzaG9sZHMgPSBzZXEoMCwxLGJ5PS4wMSkpIAoKIyBKLWluZGV4IHYuIFRocmVzaG9sZCBmb3Igbm8gZmxvcAoKdGhyZXNob2xkX291dHB1dCAlPiUKICAgIGZpbHRlcigubWV0cmljID09ICdqX2luZGV4JykgJT4lCiAgICBnZ3Bsb3QoYWVzKHggPSAudGhyZXNob2xkLCB5ID0gLmVzdGltYXRlKSkgKwogICAgZ2VvbV9saW5lKCkgKwogICAgbGFicyh5ID0gJ0otaW5kZXgnLCB4ID0gJ3RocmVzaG9sZCcpICsKICAgIHRoZW1lX2NsYXNzaWMoKQpgYGAKCmBgYHtyfQp0aHJlc2hvbGRfb3V0cHV0ICU+JQogICAgZmlsdGVyKC5tZXRyaWMgPT0gJ2pfaW5kZXgnKSAlPiUKICAgIGFycmFuZ2UoZGVzYyguZXN0aW1hdGUpKQpgYGAKCiMjIyMgRGlzdGFuY2UgdnMuIFRocmVzaG9sZAoKYGBge3J9CiMgRGlzdGFuY2UgdnMuIFRocmVzaG9sZAoKdGhyZXNob2xkX291dHB1dCAlPiUKICAgIGZpbHRlcigubWV0cmljID09ICdkaXN0YW5jZScpICU+JQogICAgZ2dwbG90KGFlcyh4ID0gLnRocmVzaG9sZCwgeSA9IC5lc3RpbWF0ZSkpICsKICAgIGdlb21fbGluZSgpICsKICAgIGxhYnMoeSA9ICdEaXN0YW5jZScsIHggPSAndGhyZXNob2xkJykgKwogICAgdGhlbWVfY2xhc3NpYygpCmBgYAoKYGBge3J9CnRocmVzaG9sZF9vdXRwdXQgJT4lCiAgICBmaWx0ZXIoLm1ldHJpYyA9PSAnZGlzdGFuY2UnKSAlPiUKICAgIGFycmFuZ2UoLmVzdGltYXRlKQpgYGAKCiMjIyBPYnRhaW4gRXZhbHVhdGlvbiBNZXRyaWNzIGZvciBMb2dpc3RpYyBSZWdyZXNzaW9uIE1vZGVsIHdpdGggVGhyZXNob2xkcwoKYGBge3J9CiMgVG8gZGV0ZXJtaW5lIGZpbmFsIHRocmVzaG9sZAoKbG9nX21ldHJpY3MgPC0gbWV0cmljX3NldChhY2N1cmFjeSxzZW5zLHlhcmRzdGljazo6c3BlYykKCiMgQ29tcGFyZSBFdmFsIE1ldHJpY3MKCmZpbmFsX291dHB1dCAlPiUKICAgIG11dGF0ZSgucHJlZF9jbGFzcyA9IG1ha2VfdHdvX2NsYXNzX3ByZWQoLnByZWRfRkFMU0UsIGxldmVscyhmbG9wKSwgdGhyZXNob2xkID0gLjc4KSkgJT4lCiAgICBsb2dfbWV0cmljcyh0cnV0aCA9IGZsb3AsIGVzdGltYXRlID0gLnByZWRfY2xhc3MsIGV2ZW50X2xldmVsID0gJ3NlY29uZCcpCgpmaW5hbF9vdXRwdXQgJT4lCiAgICBtdXRhdGUoLnByZWRfY2xhc3MgPSBtYWtlX3R3b19jbGFzc19wcmVkKC5wcmVkX0ZBTFNFLCBsZXZlbHMoZmxvcCksIHRocmVzaG9sZCA9IC43MSkpICU+JQogICAgbG9nX21ldHJpY3ModHJ1dGggPSBmbG9wLCBlc3RpbWF0ZSA9IC5wcmVkX2NsYXNzLCBldmVudF9sZXZlbCA9ICdzZWNvbmQnKQoKIyBDb21wYXJlIENvbmZ1c2lvbiBNYXRyaWNlcwoKZmluYWxfb3V0cHV0ICU+JQogIG11dGF0ZSgucHJlZF9jbGFzcyA9IG1ha2VfdHdvX2NsYXNzX3ByZWQoLnByZWRfRkFMU0UsIGxldmVscyhmbG9wKSwgdGhyZXNob2xkID0gLjc4KSkgJT4lCiAgY29uZl9tYXQodHJ1dGggPSBmbG9wLCBlc3RpbWF0ZSA9IC5wcmVkX2NsYXNzKQoKZmluYWxfb3V0cHV0ICU+JQogIG11dGF0ZSgucHJlZF9jbGFzcyA9IG1ha2VfdHdvX2NsYXNzX3ByZWQoLnByZWRfRkFMU0UsIGxldmVscyhmbG9wKSwgdGhyZXNob2xkID0gLjcxKSkgJT4lCiAgY29uZl9tYXQodHJ1dGggPSBmbG9wLCBlc3RpbWF0ZSA9IC5wcmVkX2NsYXNzKQpgYGAKCldlIGNob3NlIHRoZSB0aHJlc2hvbGQgb2YgMC43OCBmb3Igb3VyIGZpbmFsIG1vZGVsIGJlY2F1c2Ugd2UgYXJlIHByaW9yaXRpemluZyBzZW5zaXRpdml0eSBvdmVyIGFjY3VyYWN5IGFuZCBzcGVjaWZpY2l0eS4KCiMjIyBQcmVkaWN0aW9ucwoKYGBge3J9CnByZWRpY3QobG9nX2ZpdCwgbmV3X2RhdGEgPSBkYXRhLmZyYW1lKE5vX29mX1ZvdGVzID0gMTAwMDAsIFJ1bnRpbWUgPSAxMTIsIElNREJfUmF0aW5nID0gOS44LAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgR2VucmVfMSA9ICJEcmFtYSIpLCB0eXBlID0gInByb2IiCikKYGBgCgpXZSBtYW51YWxseSBwZXJmb3JtZWQgdGhlIGhhcmQgcHJlZGljdGlvbnMgd2l0aCB0aHJlc2hvbGQgPSAuNzguIFNpbmNlIC5wcmVkX0ZBTFNFID0gMC41NiB3aGljaCBpcyBsb3dlciB0aGFuIG91ciB0aHJlc2hvbGQgb2YgMC43OCwgd2Ugd291bGQgcHJlZGljdCB0aGlzIGV4YW1wbGUgbW92aWUgd291bGQgZmxvcCAoZmxvcCA9IFRSVUUpLgoKIyBVbnN1cGVydmlzZWQgTGVhcm5pbmcgLSBDbHVzdGVyaW5nCgojIyBLLU1lYW5zIENsdXN0ZXJpbmcKCiMjIyBQcmVsaW1pbmFyeSBWaXN1YWxpemF0aW9ucwoKYGBge3J9CmdncGxvdChpbWRiX2NsZWFuLCBhZXMoeCA9IEJ1ZGdldCwgeSA9IFJ1bnRpbWUpKSArIAogIGdlb21fcG9pbnQoKSArIHRoZW1lX2NsYXNzaWMoKQoKaW1kYl9jbGVhbiAlPiUKICBmaWx0ZXIoQnVkZ2V0ID4gMSkgJT4lIAogIGdncGxvdChhZXMoeCA9IEJ1ZGdldCwgeSA9IEdyb3NzKSkgKyAKICBnZW9tX3BvaW50KCkgKyAKICBsYWJzKHggPSAiQnVkZ2V0IGluIFVTRCIsIAogICAgICAgeSA9ICJHcm9zcyBQcm9maXQgaW4gVVNEIChMb2cgU2NhbGUpIiwgCiAgICAgICB0aXRsZSA9ICJQcmVsaW1pbmFyeSBWaXN1YWxpemF0aW9ucyIpICsKICB0aGVtZV9jbGFzc2ljKCkKCmdncGxvdChpbWRiX2NsZWFuLCBhZXMoeCA9IE5vX29mX1ZvdGVzLCB5ID0gUnVudGltZSkpICsgCiAgZ2VvbV9wb2ludCgpICsgdGhlbWVfY2xhc3NpYygpCgpnZ3Bsb3QoaW1kYl9jbGVhbiwgYWVzKHggPSBHcm9zcywgeSA9IE5vX29mX1ZvdGVzKSkgKyAKICBnZW9tX3BvaW50KCkgKyB0aGVtZV9jbGFzc2ljKCkKYGBgCgojIyMgRmVhdHVyZSBTZWxlY3Rpb24KCmBgYHtyfQppbWRiX3N1YiA8LSBpbWRiX2NsZWFuICU+JQogICAgc2VsZWN0KEJ1ZGdldCwgR3Jvc3MpCgpzZXQuc2VlZCgyNTMpCmBgYAoKIyMjIERldGVybWluZSBOdW1iZXIgb2YgQ2x1c3RlcnMKCmBgYHtyfQojIERhdGEtc3BlY2lmaWMgZnVuY3Rpb24gdG8gY2x1c3RlciBhbmQgY2FsY3VsYXRlIHRvdGFsIHdpdGhpbi1jbHVzdGVyIFNTCmltZGJfY2x1c3Rlcl9zcyA8LSBmdW5jdGlvbihrKXsKICAgICMgUGVyZm9ybSBjbHVzdGVyaW5nCiAgICBrY2x1c3QgPC0ga21lYW5zKHNjYWxlKGltZGJfc3ViKSwgY2VudGVycyA9IGspCgogICAgIyBSZXR1cm4gdGhlIHRvdGFsIHdpdGhpbi1jbHVzdGVyIHN1bSBvZiBzcXVhcmVzCiAgICByZXR1cm4oa2NsdXN0JHRvdC53aXRoaW5zcykKfQoKdGliYmxlKAogICAgayA9IDE6MTUsCiAgICB0b3Rfd2Nfc3MgPSBwdXJycjo6bWFwX2RibCgxOjE1LCBpbWRiX2NsdXN0ZXJfc3MpCikgJT4lIAogICAgZ2dwbG90KGFlcyh4ID0gaywgeSA9IHRvdF93Y19zcykpICsKICAgIGdlb21fcG9pbnQoKSArIAogICAgbGFicyh4ID0gIk51bWJlciBvZiBjbHVzdGVycyIseSA9ICdUb3RhbCB3aXRoaW4tY2x1c3RlciBzdW0gb2Ygc3F1YXJlcycpICsgCiAgICB0aGVtZV9jbGFzc2ljKCkKYGBgCgojIyMgU2VsZWN0IGsgPSA4IENsdXN0ZXJzCgpgYGB7cn0Ka2NsdXN0X2s4IDwtIGttZWFucyhzY2FsZShpbWRiX3N1YiksIGNlbnRlcnMgPSA4KQoKa2NsdXN0X2s4JGNsdXN0ZXIgICAjIERpc3BsYXkgY2x1c3RlciBhc3NpZ25tZW50cwoKaW1kYl9jbGVhbiA8LSBpbWRiX2NsZWFuICU+JQogICAgbXV0YXRlKGtjbHVzdF84ID0gZmFjdG9yKGtjbHVzdF9rOCRjbHVzdGVyKSkKYGBgCgojIyMgVmlzdWFsaXplIENsdXN0ZXIgQXNzaWdubWVudHMKCmBgYHtyfQojIFZpc3VhbGl6ZSB0aGUgY2x1c3RlciBhc3NpZ25tZW50cyBvbiB0aGUgb3JpZ2luYWwgc2NhdHRlcnBsb3QKaW1kYl9jbGVhbiAlPiUKICBnZ3Bsb3QoYWVzKHggPSBCdWRnZXQsIHkgPSBHcm9zcywgY29sb3IgPSBrY2x1c3RfOCkpICsKICAgIGdlb21fcG9pbnQoKSArIHRoZW1lX2NsYXNzaWMoKQpgYGAKCiMjIyBJbnRlcnByZXRpbmcgQ2x1c3RlcnMKCiMjIyMgRXhwbG9yaW5nIEdlbnJlIEJyZWFrZG93bgoKYGBge3J9CgojIENvdW50IG9mIE1vdmllcyBwZXIgR2VucmUgKFByaW1hcnkgR2VucmUpCmltZGJfY2xlYW4gJT4lCiAgY291bnQoR2VucmVfMSkKCiMgQ291bnQgb2YgTW92aWVzIHBlciBHZW5yZSAoU2Vjb25kYXJ5IEdlbnJlKQppbWRiX2NsZWFuICU+JQogIGNvdW50KEdlbnJlXzIpCgojIENvdW50IG9mIE1vdmllcyBwZXIgR2VucmUgKE92ZXJhbGwgR2VucmUpCmltZGJfY2xlYW4gJT4lCiAgY291bnQoTmV3X0dlbnJlKQpgYGAKCmBgYHtyfQojIEdlbnJlcyB2cyBDbHVzdGVyCgojIEhvdyBtYW55IG9mIGVhY2ggR2VucmUgMSBpbiBlYWNoIGNsdXN0ZXIKaW1kYl9jbGVhbiAlPiUKICBncm91cF9ieShrY2x1c3RfOCkgJT4lCiAgY291bnQoR2VucmVfMSkKCiMgSG93IG1hbnkgb2YgZWFjaCBHZW5yZSAyIGluIGVhY2ggY2x1c3RlcgppbWRiX2NsZWFuICU+JQogIGdyb3VwX2J5KGtjbHVzdF84KSAlPiUKICBjb3VudChHZW5yZV8yKQoKIyBIb3cgbWFueSBtb3ZpZXMgaW4gZWFjaCBjbHVzdGVyCmltZGJfY2xlYW4lPiUKICBjb3VudChrY2x1c3RfOCkKYGBgCgojIyMjIFZpc3VhbGl6YXRpb25zIG9mIEdlbnJlcyBpbiBFYWNoIENsdXN0ZXIKCmBgYHtyfQoKIyBHZW5yZSAxCmltZGJfY2xlYW4gJT4lCiAgZ2dwbG90KGFlcyh4ID0ga2NsdXN0XzgsIGZpbGwgPSBHZW5yZV8xKSkgKwogICAgZ2VvbV9iYXIocG9zaXRpb24gPSAiZmlsbCIpICsKICAgIGxhYnMoeCA9ICJDbHVzdGVyIikgKyAKICAgIHRoZW1lX2NsYXNzaWMoKQoKIyBHZW5yZSAyCmltZGJfY2xlYW4gJT4lCiAgZ2dwbG90KGFlcyh4ID0ga2NsdXN0XzgsIGZpbGwgPSBHZW5yZV8yKSkgKwogICAgZ2VvbV9iYXIocG9zaXRpb24gPSAiZmlsbCIpICsKICAgIGxhYnMoeCA9ICJDbHVzdGVyIikgKyAKICAgIHRoZW1lX2NsYXNzaWMoKQoKIyBPdmVyYWxsIEdlbnJlCmltZGJfY2xlYW4gJT4lCiAgZ2dwbG90KGFlcyh4ID0ga2NsdXN0XzgsIGZpbGwgPSBOZXdfR2VucmUpKSArCiAgICBnZW9tX2Jhcihwb3NpdGlvbiA9ICJmaWxsIikgKwogICAgbGFicyh4ID0gIkNsdXN0ZXIiKSArIAogICAgdGhlbWVfY2xhc3NpYygpCgpgYGAKCiMjIEhpZXJhcmNoaWNhbCBDbHVzdGVyaW5nCgojIyMgU2V0IHVwIENsdXN0ZXJpbmcgYW5kIERpc3RhbmNlIE1hdHJpeAoKYGBge3J9CiMgUmFuZG9tIHN1YnNhbXBsZSBvZiAyNSBNb3ZpZXMKc2V0LnNlZWQoMjUzKQoKaW1kYl9oYyA8LSBpbWRiX2NsZWFuICU+JQogIHNsaWNlX3NhbXBsZShuID0gMjUpICU+JQogIGZpbHRlcihCdWRnZXQgIT0gMCkKCiMgU2VsZWN0IHRoZSB2YXJpYWJsZXMgdG8gYmUgdXNlZCBpbiBjbHVzdGVyaW5nCmltZGJfaGNfc3ViIDwtIGltZGJfaGMgJT4lCiAgc2VsZWN0KEdyb3NzLCBCdWRnZXQpCgppbWRiX2hjX2Z1bGwgPC0gaW1kYl9jbGVhbiAlPiUKICBzZWxlY3QoR3Jvc3MsIEJ1ZGdldCkgJT4lCiAgZmlsdGVyKEJ1ZGdldCA+IDEpCgojIFN1bW1hcnkgc3RhdGlzdGljcyBmb3IgdGhlIHZhcmlhYmxlcwpzdW1tYXJ5KGltZGJfaGNfc3ViKQoKIyBDb21wdXRlIGEgZGlzdGFuY2UgbWF0cml4IG9uIHRoZSBzY2FsZWQgZGF0YQpkaXN0X21hdF9zY2FsZWQgPC0gZGlzdChzY2FsZShpbWRiX2hjX3N1YikpICAgICAjIFN1YnNldCBEaXN0YW5jZSBNYXRyaXgKCmRpc3RfbWF0X2Z1bGwgPC0gZGlzdChzY2FsZShpbWRiX2hjX2Z1bGwpKSAgICAgICMgRnVsbCBEYXRhIERpc3RhbmNlIE1hdHJpeApgYGAKCiMjIyBQZXJmb3JtIEZ1c2luZyBQcm9jZXNzCgpgYGB7cn0KaW1kYl9oY19hdmcgPC0gaGNsdXN0KGRpc3RfbWF0X3NjYWxlZCwgbWV0aG9kID0gImF2ZXJhZ2UiKSAgICAjIFN1YnNldAppbWRiX2Z1bGxfYXZnIDwtIGhjbHVzdChkaXN0X21hdF9mdWxsLCBtZXRob2QgPSAiYXZlcmFnZSIpICAgICMgRnVsbCBEYXRhCmBgYAoKIyMjIFZpc3VhbGl6ZSBEZW5kcm9ncmFtcwoKYGBge3J9CiMgUGxvdCBkZW5kcm9ncmFtIG9uIFN1YnNldApwbG90KGltZGJfaGNfYXZnKQpgYGAKCmBgYHtyfQojIEFkZGluZyBHZW5yZSBMYWJlbHMKCnBsb3QoaW1kYl9oY19hdmcsIGxhYmVscyA9IGltZGJfaGMkR2VucmVfMSwgbWFpbiA9ICJNb3ZpZSBDbHVzdGVycyIsIHhsYWIgPSBOVUxMKQoKcGxvdChpbWRiX2hjX2F2ZywgbGFiZWxzID0gcGFzdGUoaW1kYl9oYyRHZW5yZV8xLCBpbWRiX2hjJEdlbnJlXzIpKQoKcGxvdChpbWRiX2hjX2F2ZywgCiAgICAgbWFpbiA9ICJWaXN1YWxpemluZyBNb3ZpZSBDbHVzdGVycyIsCiAgICAgbGFiZWxzID0gcGFzdGUoaW1kYl9oYyRHZW5yZV8xLCBpbWRiX2hjJEdlbnJlXzIsIGltZGJfaGMkR2VucmVfMyksIAogICAgIGhhbmcgPSAtMSwgCiAgICAgY2V4ID0gMSkKCnBsb3QoaW1kYl9oY19hdmcsIGxhYmVscyA9IHBhc3RlKGltZGJfaGMkTmV3X0dlbnJlKSkKYGBgCgojIyMgQ3V0dGluZyB0aGUgVHJlZSAoQ2hvb3NpbmcgaykKCmBgYHtyfQppbWRiX2NsZWFuX2NsdXN0IDwtIGltZGJfY2xlYW4gJT4lCiAgZmlsdGVyKEJ1ZGdldCA+IDEpICU+JQogICAgbXV0YXRlKAogICAgICAgIGhjbHVzdF9udW0yID0gZmFjdG9yKGN1dHJlZShpbWRiX2Z1bGxfYXZnLCBrID0gMikpLCAjIEN1dCBpbnRvIDIgY2x1c3RlcnMgKGspCiAgICAgICAgaGNsdXN0X251bTQgPSBmYWN0b3IoY3V0cmVlKGltZGJfZnVsbF9hdmcsIGsgPSA0KSksICMgQ3V0IGludG8gNCBjbHVzdGVycyAoaykKICAgICAgICBoY2x1c3RfbnVtOCA9IGZhY3RvcihjdXRyZWUoaW1kYl9mdWxsX2F2ZywgayA9IDgpKSAjIEN1dCBpbnRvIDggY2x1c3RlcnMgKGspCiAgICApCmBgYAoKYGBge3J9CmdncGxvdChpbWRiX2NsZWFuX2NsdXN0LCBhZXMoeCA9IGhjbHVzdF9udW0yLCBmaWxsID0gR2VucmVfMSkpICsKICBnZW9tX2Jhcihwb3NpdGlvbiA9ICJmaWxsIikgKwogIGxhYnMoeCA9ICJDbHVzdGVyIiwgCiAgICAgICB5ID0gIlByb3BvcnRpb24gb2YgQ2x1c3RlciIsIAogICAgICAgdGl0bGUgPSAiU2VsZWN0aW5nIE51bWJlciBvZiBDbHVzdGVycyAoaykiKSArIAogIHRoZW1lX2NsYXNzaWMoKSArIAogIHRoZW1lKHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDIwLCBmYWNlID0gImJvbGQiKSkKCmdncGxvdChpbWRiX2NsZWFuX2NsdXN0LCBhZXMoeCA9IGhjbHVzdF9udW00LCBmaWxsID0gR2VucmVfMSkpICsKICBnZW9tX2Jhcihwb3NpdGlvbiA9ICJmaWxsIikgKwogIGxhYnMoeCA9ICJDbHVzdGVyIiwgCiAgICAgICB5ID0gIlByb3BvcnRpb24gb2YgQ2x1c3RlciIsIAogICAgICAgdGl0bGUgPSAiU2VsZWN0aW5nIE51bWJlciBvZiBDbHVzdGVycyAoaykiKSArIAogIHRoZW1lX2NsYXNzaWMoKSArIAogIHRoZW1lKHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDIwLCBmYWNlID0gImJvbGQiKSkKCmdncGxvdChpbWRiX2NsZWFuX2NsdXN0LCBhZXMoeCA9IGhjbHVzdF9udW04LCBmaWxsID0gR2VucmVfMSkpICsKICBnZW9tX2Jhcihwb3NpdGlvbiA9ICJmaWxsIikgKwogIGxhYnMoeCA9ICJDbHVzdGVyIiwgCiAgICAgICB5ID0gIlByb3BvcnRpb24gb2YgQ2x1c3RlciIsIAogICAgICAgdGl0bGUgPSAiU2VsZWN0aW5nIE51bWJlciBvZiBDbHVzdGVycyAoaykiKSArIAogIHRoZW1lX2NsYXNzaWMoKSArIAogIHRoZW1lKHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDIwLCBmYWNlID0gImJvbGQiKSkKYGBgCgoKIyMjIFZpc3VhbGl6aW5nIEdlbnJlcyBpbiBGaW5hbCBDbHVzdGVycyAoRnVsbCBEYXRhKQoKYGBge3J9CmdncGxvdChpbWRiX2NsZWFuX2NsdXN0LCBhZXMoeCA9IGhjbHVzdF9udW0yLCBmaWxsID0gR2VucmVfMSkpICsKICBnZW9tX2Jhcihwb3NpdGlvbiA9ICJmaWxsIikgKwogIGxhYnMoeCA9ICJDbHVzdGVyIiwgCiAgICAgICB5ID0gIlByb3BvcnRpb24gb2YgQ2x1c3RlciIsIAogICAgICAgdGl0bGUgPSAiU2VsZWN0aW5nIE51bWJlciBvZiBDbHVzdGVycyAoaykiLCAKICAgICAgIGZpbGwgPSAiR2VucmUgMSIpICsgCiAgdGhlbWVfY2xhc3NpYygpICsgCiAgdGhlbWUocGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gMjAsIGZhY2UgPSAiYm9sZCIpKQoKZ2dwbG90KGltZGJfY2xlYW5fY2x1c3QsIGFlcyh4ID0gaGNsdXN0X251bTIsIGZpbGwgPSBOZXdfR2VucmUpKSArCiAgICBnZW9tX2Jhcihwb3NpdGlvbiA9ICJmaWxsIikgKwogICAgbGFicyh4ID0gIkNsdXN0ZXIiKSArIAogICAgdGhlbWVfY2xhc3NpYygpCgpnZ3Bsb3QoaW1kYl9jbGVhbl9jbHVzdCwgYWVzKHggPSBCdWRnZXQsIHkgPSBHcm9zcywgY29sb3IgPSBoY2x1c3RfbnVtMikpICsKICBnZW9tX3BvaW50KCkgKwogIGxhYnMoeCA9ICJCdWRnZXQgaW4gVVNEIiwgCiAgICAgICB5ID0gIkdyb3NzIFByb2ZpdCBpbiBVU0QgKExvZyBTY2FsZSkiLCAKICAgICAgIHRpdGxlID0gIlZpc3VhbGl6aW5nIENsdXN0ZXJzOiBHcm9zcyBQcm9maXQgdnMuIEJ1ZGdldCIsCiAgICAgICBjb2xvciA9ICJDbHVzdGVycyIpICsgCiAgdGhlbWVfY2xhc3NpYygpICsgCiAgdGhlbWUocGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gMjAsIGZhY2UgPSAiYm9sZCIpKQoKZ2dwbG90KGltZGJfY2xlYW5fY2x1c3QsIGFlcyh4ID0gaGNsdXN0X251bTIsIHkgPSBCdWRnZXQpKSArCiAgZ2VvbV9ib3hwbG90KCkgKwogIGxhYnMoeCA9ICJDbHVzdGVyIiwgCiAgICAgICB5ID0gIkJ1ZGdldCBpbiBVU0QiLCAKICAgICAgIHRpdGxlID0gIlZpc3VhbGl6aW5nIENsdXN0ZXJzOiBCdWRnZXQiKSArIAogIHRoZW1lX2NsYXNzaWMoKSArIAogIHRoZW1lKHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDIwLCBmYWNlID0gImJvbGQiKSkKCmdncGxvdChpbWRiX2NsZWFuX2NsdXN0LCBhZXMoeCA9IGhjbHVzdF9udW0yLCB5ID0gR3Jvc3MpKSArCiAgZ2VvbV9ib3hwbG90KCkgKwogIGxhYnMoeCA9ICJDbHVzdGVyIiwgCiAgICAgICB5ID0gIkdyb3NzIFByb2ZpdCBpbiBVU0QgKExvZyBTY2FsZSkiLCAKICAgICAgIHRpdGxlID0gIlZpc3VhbGl6aW5nIENsdXN0ZXJzOiBHcm9zcyBQcm9maXQiKSArIAogIHRoZW1lX2NsYXNzaWMoKSArIAogIHRoZW1lKHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDIwLCBmYWNlID0gImJvbGQiKSkKCmltZGJfY2xlYW5fY2x1c3QgJT4lCiAgY291bnQoaGNsdXN0X251bTIpCgppbWRiX2NsZWFuX2NsdXN0ICU+JQogIGdyb3VwX2J5KGhjbHVzdF9udW0yKSAlPiUKICBzdW1tYXJpemUobWVhbihHcm9zcyksIHNkKEdyb3NzKSwgbWluKEdyb3NzKSwgbWF4KEdyb3NzKSwKICAgICAgICAgICAgbWVhbigoQnVkZ2V0LzEwMDAwMCkpLCBzZCgoQnVkZ2V0LzEwMDAwMCkpLCBtaW4oKEJ1ZGdldC8xMDAwMDApKSwgbWF4KChCdWRnZXQvMTAwMDAwKSkpCmBgYAoK